diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 5963dfc531..02020acb45 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -47,7 +47,7 @@ jobs: - name: Run tests on iOS working-directory: ${{ env.WORKING_DIRECTORY }} - run: yarn e2e:ios + run: yarn e2e:ios-ci android-e2e: if: github.repository == 'software-mansion/react-native-gesture-handler' @@ -95,4 +95,4 @@ jobs: disable-animations: true avd-name: Pixel_9 working-directory: ${{ env.WORKING_DIRECTORY }} - script: adb devices && yarn e2e:android + script: adb devices && yarn e2e:android-ci diff --git a/apps/common-app/src/e2e_screens/TestingScreen.tsx b/apps/common-app/src/e2e_screens/TestingScreen.tsx new file mode 100644 index 0000000000..02a80aff1a --- /dev/null +++ b/apps/common-app/src/e2e_screens/TestingScreen.tsx @@ -0,0 +1,72 @@ +import { StyleSheet, Text, View } from 'react-native'; +import { ScrollView, Touchable } from 'react-native-gesture-handler'; + +interface TestingScreenProps { + text: string; + buttonCallback: () => void; + children: React.ReactNode; +} + +export default function TestingScreen({ + text, + buttonCallback, + children, +}: TestingScreenProps) { + return ( + + + + {text} + + + + {children} + + + + Extract callbacks + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + scrollContainer: { + height: 50, + maxHeight: 50, + }, + innerContainer: { + flex: 1, + justifyContent: 'space-around', + alignItems: 'center', + }, + stateIndicator: { + fontSize: 15, + alignSelf: 'flex-start', + }, + buttonContainer: { + height: 60, + maxHeight: 60, + alignItems: 'center', + justifyContent: 'center', + }, + extractButton: { + width: 120, + height: 40, + borderRadius: 20, + backgroundColor: 'royalblue', + + justifyContent: 'center', + alignItems: 'center', + }, + extractButtonText: { + color: 'white', + }, +}); diff --git a/apps/common-app/src/e2e_screens/gestures/Fling.tsx b/apps/common-app/src/e2e_screens/gestures/Fling.tsx new file mode 100644 index 0000000000..930174404d --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/Fling.tsx @@ -0,0 +1,49 @@ +import { useRef, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { GestureDetector, useFlingGesture } from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; +import { CallbackIDs } from '../utils'; + +export default function FlingScreen() { + const [text, setText] = useState(''); + const callbacks = useRef(new Set()); + + const flingGesture = useFlingGesture({ + onBegin: () => { + callbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + callbacks.current.add(CallbackIDs.onActivate); + }, + onDeactivate: () => { + callbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + callbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + return ( + { + setText(`{Fling: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/gestures/LongPress.tsx b/apps/common-app/src/e2e_screens/gestures/LongPress.tsx new file mode 100644 index 0000000000..f2833f394c --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/LongPress.tsx @@ -0,0 +1,52 @@ +import { useRef, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + GestureDetector, + useLongPressGesture, +} from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; +import { CallbackIDs } from '../utils'; + +export default function LongPressScreen() { + const [text, setText] = useState(''); + const callbacks = useRef(new Set()); + + const longPressGesture = useLongPressGesture({ + onBegin: () => { + callbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + callbacks.current.add(CallbackIDs.onActivate); + }, + onDeactivate: () => { + callbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + callbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + return ( + { + setText(`{LongPress: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/gestures/Pan.tsx b/apps/common-app/src/e2e_screens/gestures/Pan.tsx index 69cecfd832..35acf0f943 100644 --- a/apps/common-app/src/e2e_screens/gestures/Pan.tsx +++ b/apps/common-app/src/e2e_screens/gestures/Pan.tsx @@ -1,101 +1,52 @@ -import { useState } from 'react'; -import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { - GestureDetector, - ScrollView, - usePanGesture, -} from 'react-native-gesture-handler'; +import { useRef, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; +import { CallbackIDs } from '../utils'; export default function PanScreen() { const [text, setText] = useState(''); + const callbacks = useRef(new Set()); const panGesture = usePanGesture({ onBegin: () => { - setText((prev) => prev + '1'); + callbacks.current.add(CallbackIDs.onBegin); }, onActivate: () => { - setText((prev) => prev + '2'); + callbacks.current.add(CallbackIDs.onActivate); }, onUpdate: () => { - setText((prev) => { - // Skip subsequent updates - if (prev[prev.length - 1] === '3') { - return prev; - } - return prev + '3'; - }); + callbacks.current.add(CallbackIDs.onUpdate); }, onDeactivate: () => { - setText((prev) => prev + '4'); + callbacks.current.add(CallbackIDs.onDeactivate); }, onFinalize: () => { - setText((prev) => prev + '5'); + callbacks.current.add(CallbackIDs.onFinalize); }, runOnJS: true, }); return ( - - - - {text} - - - - - - - - - { - setText(''); - }}> - Reset - - - + { + setText(`{Pan: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> + + + + ); } const styles = StyleSheet.create({ - container: { - flex: 1, - }, - scrollContainer: { - height: 50, - maxHeight: 50, - }, - innerContainer: { - flex: 1, - justifyContent: 'space-around', - alignItems: 'center', - }, - stateIndicator: { - fontSize: 15, - alignSelf: 'flex-start', - }, gestureBox: { width: 120, height: 120, borderRadius: 20, backgroundColor: '#4ecdc4', }, - buttonContainer: { - height: 60, - maxHeight: 60, - alignItems: 'center', - justifyContent: 'center', - }, - resetButton: { - width: 120, - height: 40, - borderRadius: 20, - backgroundColor: 'royalblue', - - justifyContent: 'center', - alignItems: 'center', - }, }); diff --git a/apps/common-app/src/e2e_screens/gestures/Pinch.tsx b/apps/common-app/src/e2e_screens/gestures/Pinch.tsx new file mode 100644 index 0000000000..fbef0b8f41 --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/Pinch.tsx @@ -0,0 +1,52 @@ +import { useRef, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { GestureDetector, usePinchGesture } from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; +import { CallbackIDs } from '../utils'; + +export default function PinchScreen() { + const [text, setText] = useState(''); + const callbacks = useRef(new Set()); + + const pinchGesture = usePinchGesture({ + onBegin: () => { + callbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + callbacks.current.add(CallbackIDs.onActivate); + }, + onUpdate: () => { + callbacks.current.add(CallbackIDs.onUpdate); + }, + onDeactivate: () => { + callbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + callbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + return ( + { + setText(`{Pinch: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/gestures/Rotation.tsx b/apps/common-app/src/e2e_screens/gestures/Rotation.tsx new file mode 100644 index 0000000000..5421f7f33a --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/Rotation.tsx @@ -0,0 +1,55 @@ +import { useRef, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + GestureDetector, + useRotationGesture, +} from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; +import { CallbackIDs } from '../utils'; + +export default function RotationScreen() { + const [text, setText] = useState(''); + const callbacks = useRef(new Set()); + + const rotationGesture = useRotationGesture({ + onBegin: () => { + callbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + callbacks.current.add(CallbackIDs.onActivate); + }, + onUpdate: () => { + callbacks.current.add(CallbackIDs.onUpdate); + }, + onDeactivate: () => { + callbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + callbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + return ( + { + setText(`{Rotation: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/gestures/Tap.tsx b/apps/common-app/src/e2e_screens/gestures/Tap.tsx new file mode 100644 index 0000000000..283a58fad2 --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/Tap.tsx @@ -0,0 +1,49 @@ +import { useRef, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { GestureDetector, useTapGesture } from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; +import { CallbackIDs } from '../utils'; + +export default function TapScreen() { + const [text, setText] = useState(''); + const callbacks = useRef(new Set()); + + const tapGesture = useTapGesture({ + onBegin: () => { + callbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + callbacks.current.add(CallbackIDs.onActivate); + }, + onDeactivate: () => { + callbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + callbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + return ( + { + setText(`{Tap: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/relations/Competing.tsx b/apps/common-app/src/e2e_screens/relations/Competing.tsx new file mode 100644 index 0000000000..978ebc1fd9 --- /dev/null +++ b/apps/common-app/src/e2e_screens/relations/Competing.tsx @@ -0,0 +1,82 @@ +import { useRef, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + GestureDetector, + useCompetingGestures, + usePanGesture, + useTapGesture, +} from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; +import { CallbackIDs } from '../utils'; + +export default function CompetingScreen() { + const [text, setText] = useState(''); + + const panCallbacks = useRef(new Set()); + const tapCallbacks = useRef(new Set()); + + const tapGesture = useTapGesture({ + onBegin: () => { + tapCallbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + tapCallbacks.current.add(CallbackIDs.onActivate); + }, + onDeactivate: () => { + tapCallbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + tapCallbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + const panGesture = usePanGesture({ + onBegin: () => { + panCallbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + panCallbacks.current.add(CallbackIDs.onActivate); + }, + onUpdate: () => { + panCallbacks.current.add(CallbackIDs.onUpdate); + }, + onDeactivate: () => { + panCallbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + panCallbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + const g = useCompetingGestures(tapGesture, panGesture); + + return ( + { + const panCallbacksText = Array.from(panCallbacks.current).join(''); + const tapCallbacksText = Array.from(tapCallbacks.current).join(''); + + setText(`{Pan: ${panCallbacksText}, Tap: ${tapCallbacksText}}`); + + panCallbacks.current.clear(); + tapCallbacks.current.clear(); + }}> + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/relations/Exclusive.tsx b/apps/common-app/src/e2e_screens/relations/Exclusive.tsx new file mode 100644 index 0000000000..6a5c8bd2b0 --- /dev/null +++ b/apps/common-app/src/e2e_screens/relations/Exclusive.tsx @@ -0,0 +1,83 @@ +import { useRef, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + GestureDetector, + useExclusiveGestures, + useTapGesture, +} from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; +import { CallbackIDs } from '../utils'; + +export default function ExclusiveScreen() { + const [text, setText] = useState(''); + + const tapCallbacks = useRef(new Set()); + const doubleTapCallbacks = useRef(new Set()); + + const doubleTapGesture = useTapGesture({ + numberOfTaps: 2, + onBegin: () => { + doubleTapCallbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + doubleTapCallbacks.current.add(CallbackIDs.onActivate); + }, + onDeactivate: () => { + doubleTapCallbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + doubleTapCallbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + const tapGesture = useTapGesture({ + onBegin: () => { + tapCallbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + tapCallbacks.current.add(CallbackIDs.onActivate); + }, + onDeactivate: () => { + tapCallbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + tapCallbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + const g = useExclusiveGestures(doubleTapGesture, tapGesture); + + return ( + { + const tapCallbacksText = Array.from(tapCallbacks.current).join(''); + const doubleTapCallbacksText = Array.from( + doubleTapCallbacks.current + ).join(''); + + setText( + `{DoubleTap: ${doubleTapCallbacksText}, Tap: ${tapCallbacksText}}` + ); + + tapCallbacks.current.clear(); + doubleTapCallbacks.current.clear(); + }}> + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/relations/Simultaneous.tsx b/apps/common-app/src/e2e_screens/relations/Simultaneous.tsx new file mode 100644 index 0000000000..c99de7b0ae --- /dev/null +++ b/apps/common-app/src/e2e_screens/relations/Simultaneous.tsx @@ -0,0 +1,84 @@ +import { useRef, useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + GestureDetector, + usePanGesture, + useSimultaneousGestures, +} from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; +import { CallbackIDs } from '../utils'; + +export default function SimultaneousScreen() { + const [text, setText] = useState(''); + + const pan1Callbacks = useRef(new Set()); + const pan2Callbacks = useRef(new Set()); + + const pan1Gesture = usePanGesture({ + onBegin: () => { + pan1Callbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + pan1Callbacks.current.add(CallbackIDs.onActivate); + }, + onUpdate: () => { + pan1Callbacks.current.add(CallbackIDs.onUpdate); + }, + onDeactivate: () => { + pan1Callbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + pan1Callbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + const pan2Gesture = usePanGesture({ + onBegin: () => { + pan2Callbacks.current.add(CallbackIDs.onBegin); + }, + onActivate: () => { + pan2Callbacks.current.add(CallbackIDs.onActivate); + }, + onUpdate: () => { + pan2Callbacks.current.add(CallbackIDs.onUpdate); + }, + onDeactivate: () => { + pan2Callbacks.current.add(CallbackIDs.onDeactivate); + }, + onFinalize: () => { + pan2Callbacks.current.add(CallbackIDs.onFinalize); + }, + runOnJS: true, + }); + + const g = useSimultaneousGestures(pan1Gesture, pan2Gesture); + + return ( + { + const pan1CallbacksText = Array.from(pan1Callbacks.current).join(''); + const pan2CallbacksText = Array.from(pan2Callbacks.current).join(''); + + setText(`{Pan1: ${pan1CallbacksText}, Pan2: ${pan2CallbacksText}}`); + + pan1Callbacks.current.clear(); + pan2Callbacks.current.clear(); + }}> + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/utils.ts b/apps/common-app/src/e2e_screens/utils.ts new file mode 100644 index 0000000000..14bc515019 --- /dev/null +++ b/apps/common-app/src/e2e_screens/utils.ts @@ -0,0 +1,19 @@ +export const TestScreens = { + Pan: '[E2E] Pan', + Tap: '[E2E] Tap', + LongPress: '[E2E] LongPress', + Fling: '[E2E] Fling', + Pinch: '[E2E] Pinch', + Rotation: '[E2E] Rotation', + Competing: '[E2E] Competing', + Exclusive: '[E2E] Exclusive', + Simultaneous: '[E2E] Simultaneous', +}; + +export const CallbackIDs = { + onBegin: '1', + onActivate: '2', + onUpdate: '3', + onDeactivate: '4', + onFinalize: '5', +}; diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index 9c5f560c13..6e793e77e7 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -1,5 +1,14 @@ import type { ExamplesSection } from '../common'; +import FlingE2E from '../e2e_screens/gestures/Fling'; +import LongPressE2E from '../e2e_screens/gestures/LongPress'; import PanE2E from '../e2e_screens/gestures/Pan'; +import PinchE2E from '../e2e_screens/gestures/Pinch'; +import RotationE2E from '../e2e_screens/gestures/Rotation'; +import TapE2E from '../e2e_screens/gestures/Tap'; +import CompetingE2E from '../e2e_screens/relations/Competing'; +import ExclusiveE2E from '../e2e_screens/relations/Exclusive'; +import SimultaneousE2E from '../e2e_screens/relations/Simultaneous'; +import { TestScreens } from '../e2e_screens/utils'; import EmptyExample from '../empty'; import CameraExample from './complicated/camera'; import ChatHeadsExample from './complicated/chat_heads'; @@ -137,6 +146,16 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ }, { sectionTitle: 'E2E Tests', - data: [{ name: 'Pan Gesture', component: PanE2E }], + data: [ + { name: TestScreens.Pan, component: PanE2E }, + { name: TestScreens.Pinch, component: PinchE2E }, + { name: TestScreens.Rotation, component: RotationE2E }, + { name: TestScreens.Tap, component: TapE2E }, + { name: TestScreens.LongPress, component: LongPressE2E }, + { name: TestScreens.Fling, component: FlingE2E }, + { name: TestScreens.Competing, component: CompetingE2E }, + { name: TestScreens.Exclusive, component: ExclusiveE2E }, + { name: TestScreens.Simultaneous, component: SimultaneousE2E }, + ], }, ]; diff --git a/apps/expo-example/e2e/__tests__/all.test.ts b/apps/expo-example/e2e/__tests__/all.test.ts new file mode 100644 index 0000000000..99d4a639a9 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/all.test.ts @@ -0,0 +1,19 @@ +import { flingTests } from '../suites/gestures/fling'; +import { longPressTests } from '../suites/gestures/long_press'; +import { panTests } from '../suites/gestures/pan'; +import { pinchTests } from '../suites/gestures/pinch'; +import { rotationTests } from '../suites/gestures/rotation'; +import { tapTests } from '../suites/gestures/tap'; +import { competingTests } from '../suites/relations/competing'; +import { exclusiveTests } from '../suites/relations/exclusive'; +import { simultaneousTests } from '../suites/relations/simultaneous'; + +tapTests(); +pinchTests(); +flingTests(); +longPressTests(); +rotationTests(); +panTests(); +competingTests(); +exclusiveTests(); +simultaneousTests(); diff --git a/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts b/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts new file mode 100644 index 0000000000..b90e788034 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts @@ -0,0 +1,15 @@ +import { flingTests } from '../suites/gestures/fling'; +import { longPressTests } from '../suites/gestures/long_press'; +import { panTests } from '../suites/gestures/pan'; +import { tapTests } from '../suites/gestures/tap'; +import { competingTests } from '../suites/relations/competing'; +import { exclusiveTests } from '../suites/relations/exclusive'; +import { simultaneousTests } from '../suites/relations/simultaneous'; + +tapTests(); +flingTests(); +longPressTests(); +panTests(); +competingTests(); +exclusiveTests(); +simultaneousTests(); diff --git a/apps/expo-example/e2e/__tests__/gestures/fling.test.ts b/apps/expo-example/e2e/__tests__/gestures/fling.test.ts new file mode 100644 index 0000000000..66cea816a2 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/fling.test.ts @@ -0,0 +1,3 @@ +import { flingTests } from '../../suites/gestures/fling'; + +flingTests(); diff --git a/apps/expo-example/e2e/__tests__/gestures/long_press.test.ts b/apps/expo-example/e2e/__tests__/gestures/long_press.test.ts new file mode 100644 index 0000000000..d3e5455e7e --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/long_press.test.ts @@ -0,0 +1,3 @@ +import { longPressTests } from '../../suites/gestures/long_press'; + +longPressTests(); diff --git a/apps/expo-example/e2e/__tests__/gestures/pan.test.ts b/apps/expo-example/e2e/__tests__/gestures/pan.test.ts new file mode 100644 index 0000000000..0451b22c71 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/pan.test.ts @@ -0,0 +1,3 @@ +import { panTests } from '../../suites/gestures/pan'; + +panTests(); diff --git a/apps/expo-example/e2e/__tests__/gestures/pinch.test.ts b/apps/expo-example/e2e/__tests__/gestures/pinch.test.ts new file mode 100644 index 0000000000..9876ea71ce --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/pinch.test.ts @@ -0,0 +1,3 @@ +import { pinchTests } from '../../suites/gestures/pinch'; + +pinchTests(); diff --git a/apps/expo-example/e2e/__tests__/gestures/rotation.test.ts b/apps/expo-example/e2e/__tests__/gestures/rotation.test.ts new file mode 100644 index 0000000000..3949a22b66 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/rotation.test.ts @@ -0,0 +1,3 @@ +import { rotationTests } from '../../suites/gestures/rotation'; + +rotationTests(); diff --git a/apps/expo-example/e2e/__tests__/gestures/tap.test.ts b/apps/expo-example/e2e/__tests__/gestures/tap.test.ts new file mode 100644 index 0000000000..9a70b42174 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/tap.test.ts @@ -0,0 +1,3 @@ +import { tapTests } from '../../suites/gestures/tap'; + +tapTests(); diff --git a/apps/expo-example/e2e/__tests__/pan.test.ts b/apps/expo-example/e2e/__tests__/pan.test.ts deleted file mode 100644 index 9b17b6e81c..0000000000 --- a/apps/expo-example/e2e/__tests__/pan.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -// eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { by, element, expect } from 'detox'; - -import { navigateTo } from '../utils'; - -describe('test pan gesture', () => { - beforeAll(async () => { - await navigateTo('Pan Gesture'); - }); - - const gestureBox = element(by.id('pan-box')); - const stateIndicator = element(by.id('state-indicator')); - const resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); - - test("Shouldn't register a pan gesture on tap", async () => { - await gestureBox.tap(); - await expect(stateIndicator).toHaveText('15'); - }); - - test('Should register pan gesture on swipe', async () => { - await gestureBox.swipe('right', 'fast'); - await expect(stateIndicator).toHaveText('12345'); - }); -}); diff --git a/apps/expo-example/e2e/__tests__/relations/competing.test.ts b/apps/expo-example/e2e/__tests__/relations/competing.test.ts new file mode 100644 index 0000000000..755016bfef --- /dev/null +++ b/apps/expo-example/e2e/__tests__/relations/competing.test.ts @@ -0,0 +1,3 @@ +import { competingTests } from '../../suites/relations/competing'; + +competingTests(); diff --git a/apps/expo-example/e2e/__tests__/relations/exclusive.test.ts b/apps/expo-example/e2e/__tests__/relations/exclusive.test.ts new file mode 100644 index 0000000000..4b5f0158b8 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/relations/exclusive.test.ts @@ -0,0 +1,3 @@ +import { exclusiveTests } from '../../suites/relations/exclusive'; + +exclusiveTests(); diff --git a/apps/expo-example/e2e/__tests__/relations/simultaneous.test.ts b/apps/expo-example/e2e/__tests__/relations/simultaneous.test.ts new file mode 100644 index 0000000000..0ca240df2f --- /dev/null +++ b/apps/expo-example/e2e/__tests__/relations/simultaneous.test.ts @@ -0,0 +1,3 @@ +import { simultaneousTests } from '../../suites/relations/simultaneous'; + +simultaneousTests(); diff --git a/apps/expo-example/e2e/suites/gestures/fling.ts b/apps/expo-example/e2e/suites/gestures/fling.ts new file mode 100644 index 0000000000..31544666e0 --- /dev/null +++ b/apps/expo-example/e2e/suites/gestures/fling.ts @@ -0,0 +1,31 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, describe } from '@jest/globals'; +import { by, element, expect } from 'detox'; + +import { CB, navigateTo, TestScreens } from '../../utils'; + +export function flingTests() { + describe('test fling gesture', () => { + beforeAll(async () => { + await navigateTo(TestScreens.Fling); + }); + + const gestureBox = element(by.id('fling-box')); + const stateIndicator = element(by.id('state-indicator')); + const extractButton = element(by.id('extract-button')); + + test('Should register fling gesture', async () => { + await gestureBox.swipe('right', 'fast'); + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{Fling: ${CB.B}${CB.A}${CB.D}${CB.F}}` + ); + }); + + test("Shouldn't register fling gesture", async () => { + await gestureBox.tap(); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{Fling: ${CB.B}${CB.F}}`); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/gestures/long_press.ts b/apps/expo-example/e2e/suites/gestures/long_press.ts new file mode 100644 index 0000000000..a05d7e1ab4 --- /dev/null +++ b/apps/expo-example/e2e/suites/gestures/long_press.ts @@ -0,0 +1,31 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, describe } from '@jest/globals'; +import { by, element, expect } from 'detox'; + +import { CB, navigateTo, TestScreens } from '../../utils'; + +export function longPressTests() { + describe('test long press gesture', () => { + beforeAll(async () => { + await navigateTo(TestScreens.LongPress); + }); + + const gestureBox = element(by.id('long-press-box')); + const stateIndicator = element(by.id('state-indicator')); + const extractButton = element(by.id('extract-button')); + + test('Should register long press gesture', async () => { + await gestureBox.longPress(1000); + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{LongPress: ${CB.B}${CB.A}${CB.D}${CB.F}}` + ); + }); + + test("Shouldn't register tap gesture", async () => { + await gestureBox.tap(); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{LongPress: ${CB.B}${CB.F}}`); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/gestures/pan.ts b/apps/expo-example/e2e/suites/gestures/pan.ts new file mode 100644 index 0000000000..851c839f5d --- /dev/null +++ b/apps/expo-example/e2e/suites/gestures/pan.ts @@ -0,0 +1,31 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, describe } from '@jest/globals'; +import { by, element, expect } from 'detox'; + +import { CB, navigateTo, TestScreens } from '../../utils'; + +export function panTests() { + describe('test pan gesture', () => { + beforeAll(async () => { + await navigateTo(TestScreens.Pan); + }); + + const gestureBox = element(by.id('pan-box')); + const stateIndicator = element(by.id('state-indicator')); + const extractButton = element(by.id('extract-button')); + + test("Shouldn't register a pan gesture on tap", async () => { + await gestureBox.tap(); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{Pan: ${CB.B}${CB.F}}`); + }); + + test('Should register pan gesture on swipe', async () => { + await gestureBox.swipe('right', 'fast'); + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{Pan: ${CB.B}${CB.A}${CB.U}${CB.D}${CB.F}}` + ); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/gestures/pinch.ts b/apps/expo-example/e2e/suites/gestures/pinch.ts new file mode 100644 index 0000000000..f9096584b4 --- /dev/null +++ b/apps/expo-example/e2e/suites/gestures/pinch.ts @@ -0,0 +1,77 @@ +import { spawn } from 'node:child_process'; + +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, describe } from '@jest/globals'; +import { by, element, expect } from 'detox'; + +import { CB, navigateTo, TestScreens } from '../../utils'; + +type PinchArgs = { + centerX: string; + centerY: string; + startDistance: string; + endDistance: string; +}; + +function argentPinch(udid: string, pa: PinchArgs): Promise { + const child = spawn('argent', [ + 'run', + 'gesture-pinch', + '--udid', + udid, + '--centerX', + pa.centerX, + '--centerY', + pa.centerY, + '--startDistance', + pa.startDistance, + '--endDistance', + pa.endDistance, + ]); + return new Promise((resolve, reject) => { + child.on('exit', (code) => { + if (code === 0) { + resolve(true); + } else { + reject(new Error(`Argent process exited with code ${code}`)); + } + }); + child.on('error', (err) => { + reject(err); + }); + }); +} + +export function pinchTests() { + describe('test pinch gesture', () => { + beforeAll(async () => { + await navigateTo(TestScreens.Pinch); + }); + + const gestureBox = element(by.id('pinch-box')); + const stateIndicator = element(by.id('state-indicator')); + const extractButton = element(by.id('extract-button')); + + test('Should register pinch gesture on pinch', async () => { + const udid = device.id; + + await argentPinch(udid, { + centerX: '0.5', + centerY: '0.55', + startDistance: '0.2', + endDistance: '0.6', + }); + + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{Pinch: ${CB.B}${CB.A}${CB.U}${CB.D}${CB.F}}` + ); + }); + + test('Shouldn`t register a pinch gesture on tap', async () => { + await gestureBox.tap(); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{Pinch: }`); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/gestures/rotation.ts b/apps/expo-example/e2e/suites/gestures/rotation.ts new file mode 100644 index 0000000000..8827e406bb --- /dev/null +++ b/apps/expo-example/e2e/suites/gestures/rotation.ts @@ -0,0 +1,81 @@ +import { spawn } from 'node:child_process'; + +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, describe } from '@jest/globals'; +import { by, element, expect } from 'detox'; + +import { CB, navigateTo, TestScreens } from '../../utils'; + +type RotationArgs = { + centerX: string; + centerY: string; + radius: string; + startAngle: string; + endAngle: string; +}; + +function argentRotate(udid: string, ra: RotationArgs): Promise { + const child = spawn('argent', [ + 'run', + 'gesture-rotate', + '--udid', + udid, + '--centerX', + ra.centerX, + '--centerY', + ra.centerY, + '--startAngle', + ra.startAngle, + '--endAngle', + ra.endAngle, + '--radius', + ra.radius, + ]); + return new Promise((resolve, reject) => { + child.on('exit', (code) => { + if (code === 0) { + resolve(true); + } else { + reject(new Error(`Argent process exited with code ${code}`)); + } + }); + child.on('error', (err) => { + reject(err); + }); + }); +} + +export function rotationTests() { + describe('test rotation gesture', () => { + beforeAll(async () => { + await navigateTo(TestScreens.Rotation); + }); + + const gestureBox = element(by.id('rotation-box')); + const stateIndicator = element(by.id('state-indicator')); + const extractButton = element(by.id('extract-button')); + + test('Should register rotation gesture on rotation', async () => { + const udid = device.id; + + await argentRotate(udid, { + centerX: '0.5', + centerY: '0.55', + startAngle: '0', + endAngle: '90', + radius: '0.05', + }); + + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{Rotation: ${CB.B}${CB.A}${CB.U}${CB.D}${CB.F}}` + ); + }); + + test('Shouldn`t register a rotation gesture on tap', async () => { + await gestureBox.tap(); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{Rotation: }`); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/gestures/tap.ts b/apps/expo-example/e2e/suites/gestures/tap.ts new file mode 100644 index 0000000000..23b75d7001 --- /dev/null +++ b/apps/expo-example/e2e/suites/gestures/tap.ts @@ -0,0 +1,31 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, describe } from '@jest/globals'; +import { by, element, expect } from 'detox'; + +import { CB, navigateTo, TestScreens } from '../../utils'; + +export function tapTests() { + describe('test tap gesture', () => { + beforeAll(async () => { + await navigateTo(TestScreens.Tap); + }); + + const gestureBox = element(by.id('tap-box')); + const stateIndicator = element(by.id('state-indicator')); + const extractButton = element(by.id('extract-button')); + + test('Should register tap gesture', async () => { + await gestureBox.tap(); + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{Tap: ${CB.B}${CB.A}${CB.D}${CB.F}}` + ); + }); + + test('Should register tap gesture on long press', async () => { + await gestureBox.longPress(1000); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{Tap: ${CB.B}${CB.F}}`); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/relations/competing.ts b/apps/expo-example/e2e/suites/relations/competing.ts new file mode 100644 index 0000000000..f86df993d5 --- /dev/null +++ b/apps/expo-example/e2e/suites/relations/competing.ts @@ -0,0 +1,33 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, describe } from '@jest/globals'; +import { by, element, expect } from 'detox'; + +import { CB, navigateTo, TestScreens } from '../../utils'; + +export function competingTests() { + describe('test competing gestures', () => { + beforeAll(async () => { + await navigateTo(TestScreens.Competing); + }); + + const gestureBox = element(by.id('competing-box')); + const stateIndicator = element(by.id('state-indicator')); + const extractButton = element(by.id('extract-button')); + + test('Should activate tap gesture on tap', async () => { + await gestureBox.tap(); + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{Pan: ${CB.B}${CB.F}, Tap: ${CB.B}${CB.A}${CB.D}${CB.F}}` + ); + }); + + test('Should activate pan gesture on swipe', async () => { + await gestureBox.swipe('right', 'fast'); + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{Pan: ${CB.B}${CB.A}${CB.U}${CB.D}${CB.F}, Tap: ${CB.B}${CB.F}}` + ); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/relations/exclusive.ts b/apps/expo-example/e2e/suites/relations/exclusive.ts new file mode 100644 index 0000000000..eee32303a3 --- /dev/null +++ b/apps/expo-example/e2e/suites/relations/exclusive.ts @@ -0,0 +1,33 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, describe } from '@jest/globals'; +import { by, element, expect } from 'detox'; + +import { CB, navigateTo, TestScreens } from '../../utils'; + +export function exclusiveTests() { + describe('test exclusive gestures (tap + double tap)', () => { + beforeAll(async () => { + await navigateTo(TestScreens.Exclusive); + }); + + const gestureBox = element(by.id('exclusive-box')); + const stateIndicator = element(by.id('state-indicator')); + const extractButton = element(by.id('extract-button')); + + test('Should activate only tap gesture on single tap', async () => { + await gestureBox.tap(); + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{DoubleTap: ${CB.B}${CB.F}, Tap: ${CB.B}${CB.A}${CB.D}${CB.F}}` + ); + }); + + test('Should activate only double tap gesture on double tap', async () => { + await gestureBox.multiTap(2); + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{DoubleTap: ${CB.B}${CB.A}${CB.D}${CB.F}, Tap: ${CB.B}${CB.F}}` + ); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/relations/simultaneous.ts b/apps/expo-example/e2e/suites/relations/simultaneous.ts new file mode 100644 index 0000000000..4810843a67 --- /dev/null +++ b/apps/expo-example/e2e/suites/relations/simultaneous.ts @@ -0,0 +1,25 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, describe } from '@jest/globals'; +import { by, element, expect } from 'detox'; + +import { CB, navigateTo, TestScreens } from '../../utils'; + +export function simultaneousTests() { + describe('test simultaneous gestures', () => { + beforeAll(async () => { + await navigateTo(TestScreens.Simultaneous); + }); + + const gestureBox = element(by.id('simultaneous-box')); + const stateIndicator = element(by.id('state-indicator')); + const extractButton = element(by.id('extract-button')); + + test('Should activate both pan gestures on swipe', async () => { + await gestureBox.swipe('right', 'fast'); + await extractButton.tap(); + await expect(stateIndicator).toHaveText( + `{Pan1: ${CB.B}${CB.A}${CB.U}${CB.D}${CB.F}, Pan2: ${CB.B}${CB.A}${CB.U}${CB.D}${CB.F}}` + ); + }); + }); +} diff --git a/apps/expo-example/e2e/utils.ts b/apps/expo-example/e2e/utils.ts index c0422d2714..0d37fa2d8f 100644 --- a/apps/expo-example/e2e/utils.ts +++ b/apps/expo-example/e2e/utils.ts @@ -1,3 +1,4 @@ +import { CallbackIDs } from 'common-app/src/e2e_screens/utils'; import { by, device, element, waitFor } from 'detox'; export async function navigateTo(screenName: string) { @@ -13,3 +14,13 @@ export async function navigateTo(screenName: string) { .scroll(2000, 'down'); await element(by.text(screenName)).tap(); } + +export const CB = { + B: CallbackIDs.onBegin, + A: CallbackIDs.onActivate, + U: CallbackIDs.onUpdate, + D: CallbackIDs.onDeactivate, + F: CallbackIDs.onFinalize, +}; + +export { TestScreens } from 'common-app/src/e2e_screens/utils'; diff --git a/apps/expo-example/package.json b/apps/expo-example/package.json index 4109d367d7..f08db5d08c 100644 --- a/apps/expo-example/package.json +++ b/apps/expo-example/package.json @@ -13,8 +13,10 @@ "build-detox-ios": "yarn detox build -c ios.sim.release", "build-detox": "yarn build-detox-android && yarn build-detox-ios", "test": "jest", - "e2e:ios": "detox test -c ios.sim.release e2e/__tests__/pan.test.ts", - "e2e:android": "detox test -c android.emu.release e2e/__tests__/pan.test.ts" + "e2e:ios": "detox test -c ios.sim.release e2e/__tests__/all.test.ts", + "e2e:ios-ci": "detox test -c ios.sim.release e2e/__tests__/all_wo_argent.test.ts", + "e2e:android": "detox test -c android.emu.release e2e/__tests__/all.test.ts", + "e2e:android-ci": "detox test -c android.emu.release e2e/__tests__/all_wo_argent.test.ts" }, "dependencies": { "@expo/metro-runtime": "~56.0.10",