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",