* Copyright (c) 2024 Huawei Technologies Co., Ltd.
*
* This source code is licensed under the MIT license found in the
* LICENSE-MIT file in the root directory of this source tree.
*/
import { TestCase, TestSuite } from '@rnoh/testerino';
import { useEffect, useRef, useState } from 'react';
import {
Animated,
PanResponder,
ScrollView,
Text,
TextInput,
TouchableOpacity,
View,
ViewStyle,
} from 'react-native';
import { Button } from '../components';
import React from 'react';
export function TouchHandlingTest() {
return (
<TestSuite name="Touch Handling">
<TestCase
itShould="pass when pressed red rectangle"
initialState={false}
arrange={({ setState }) => {
return (
<TouchIssue1
onPress={() => {
setState(true);
}}
/>
);
}}
assert={({ expect, state }) => {
expect(state).to.be.true;
}}
/>
<TestCase
itShould="register a touch after native transform animation"
initialState={false}
arrange={({ setState }) => (
<RectangleSlider
onPress={() => {
setState(prev => !prev);
}}
/>
)}
assert={({ expect, state }) => {
expect(state).to.be.true;
}}
/>
<TestCase
itShould="handle press on rotated view"
initialState={false}
arrange={({ setState }) => (
<TouchableTransformedTest
setState={setState}
transform={[{ rotate: '180deg' }, { translateX: 100 }]}
/>
)}
assert={({ expect, state }) => {
expect(state).to.be.true;
}}
/>
<TestCase
itShould="handle press on scaled view"
initialState={false}
arrange={({ setState }) => (
<TouchableTransformedTest
setState={setState}
transform={[{ scaleX: -1 }, { translateX: 100 }]}
/>
)}
assert={({ expect, state }) => {
expect(state).to.be.true;
}}
/>
<TestCase itShould="report transformed touch coordinates">
<TouchCoordinatesTest
transform={[
{ rotate: '45deg' },
{ translateY: 50 },
{ translateX: -50 },
{ scaleY: -1 },
{ scale: 0.75 },
]}
/>
</TestCase>
<TestCase itShould="report transformed touch coordinates">
<TouchCoordinatesTest
transform={[
{ rotate: '-45deg' },
{ translateY: 50 },
{ translateX: -50 },
{ scaleX: -1 },
{ scaleY: 1.25 },
]}
/>
</TestCase>
<TestCase
itShould="respond to touches on disabled components when wrapped in Touchables"
initialState={false}
arrange={({ setState }) => (
<TouchableOpacity
onPress={() => {
setState(true);
}}>
<TextInput
editable={false}
style={{
borderWidth: 2,
borderColor: 'blue',
}}
value={'Non-editable TextInput'}
/>
</TouchableOpacity>
)}
assert={({ expect, state }) => {
expect(state).to.be.true;
}}
/>
<TestCase
modal
itShould="allow vertical scroll when flinging fast after horizontal swipe on gray area">
<ScrollViewLockedIssue />
</TestCase>
</TestSuite>
);
}
function RectangleSlider(props: { onPress: () => void }) {
const square1Anim = useRef(new Animated.Value(0)).current;
const animation = Animated.timing(square1Anim, {
toValue: 64,
duration: 1000,
useNativeDriver: true,
});
const handleAnimation = () => {
animation.reset();
animation.start();
};
return (
<>
<Animated.View
onTouchEnd={() => {
props.onPress();
}}
style={{
height: 64,
width: 64,
backgroundColor: 'red',
transform: [
{
translateX: square1Anim,
},
],
}}
/>
<Button label="Animate" onPress={handleAnimation} />
</>
);
}
function TouchableTransformedTest({
setState,
transform,
}: {
setState: (v: boolean) => void;
transform: ViewStyle['transform'];
}) {
return (
<View
style={{
alignSelf: 'center',
width: 75,
backgroundColor: 'red',
transform,
}}>
<TouchableOpacity onPress={() => setState(true)}>
<Text>Press me!</Text>
</TouchableOpacity>
</View>
);
}
function TouchCoordinatesTest({
transform,
}: {
transform?: ViewStyle['transform'];
}) {
const [position, setPosition] = React.useState({ x: 0, y: 0 });
return (
<View style={{ height: 250 }}>
<Text>Touch coordinates: {JSON.stringify(position)}</Text>
<View
style={{
alignSelf: 'center',
width: 150,
height: 150,
backgroundColor: 'red',
transform,
opacity: 0.5,
}}
onTouchStart={e => {
setPosition({
x: Math.round(e.nativeEvent.locationX),
y: Math.round(e.nativeEvent.locationY),
});
}}
onTouchMove={e => {
setPosition({
x: Math.round(e.nativeEvent.locationX),
y: Math.round(e.nativeEvent.locationY),
});
}}>
<Text>Top left</Text>
</View>
</View>
);
}
const TouchIssue1 = ({ onPress }: { onPress: () => void }) => {
const nPressesRef = useRef(0);
const [nRenders, setNRenders] = useState(0);
const [label, setLabel] = useState('hello');
useEffect(() => {
const timeout = setTimeout(() => {
setNRenders(1);
}, 2000);
return () => {
clearTimeout(timeout);
};
}, []);
if (nRenders > 0) {
return (
<View style={{ opacity: 1, marginTop: 50 }}>
<View collapsable={false}>
<TouchableOpacity
onPress={() => {
onPress();
setLabel(`${label}+${nPressesRef.current}`);
nPressesRef.current++;
}}>
<Text>{label}</Text>
<View
id="foo"
style={{ height: 100, width: 100, backgroundColor: 'red' }}
/>
</TouchableOpacity>
</View>
</View>
);
} else {
return (
<View style={{ opacity: 0, marginTop: 50 }}>
<View collapsable={false}>
<View style={{ height: 100, width: 100 }} />
</View>
</View>
);
}
};
class ScrollViewLockedIssue extends React.Component {
textInput: any;
blur = () => {
this.textInput.blur();
};
_gestureHandlers: any;
componentWillMount() {
this._gestureHandlers = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onStartShouldSetPanResponderCapture: () => false,
onMoveShouldSetPanResponder: (event, gestureState) => {
const touchCapture =
Math.abs(gestureState.dx) > 10 || Math.abs(gestureState.dy) > 10;
return touchCapture;
},
onMoveShouldSetPanResponderCapture: () => false,
onPanResponderMove: (_evt, _gestureState) => {
console.warn('move');
},
});
}
render(): React.ReactNode {
return (
<ScrollView style={{ height: '75%' }}>
<View style={{ height: 100 }}>
<Text>this is first part</Text>
</View>
<View
style={{ height: 300, backgroundColor: 'green' }}
{...this._gestureHandlers.panHandlers}>
<Text>this is second part</Text>
</View>
<View
style={{
height: 800,
backgroundColor: 'yellow',
}}
{...this._gestureHandlers.panHandlers}>
<TouchableOpacity>
<Text style={{ backgroundColor: 'gray', height: 100 }}>
SWIPE HORIZONTALLY HERE
</Text>
</TouchableOpacity>
</View>
</ScrollView>
);
}
}