From 3c5f7ac9624f7c8a5a55dbb3df4e47701c2f1253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 May 2026 12:01:42 +0200 Subject: [PATCH 01/22] Add common base --- .../src/e2e_screens/TestingScreen.tsx | 71 +++++++++++++++++++ .../src/e2e_screens/gestures/pan.tsx | 68 +++--------------- 2 files changed, 80 insertions(+), 59 deletions(-) create mode 100644 apps/common-app/src/e2e_screens/TestingScreen.tsx 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..63f3ee0efa --- /dev/null +++ b/apps/common-app/src/e2e_screens/TestingScreen.tsx @@ -0,0 +1,71 @@ +import { Pressable, StyleSheet, Text, View } from 'react-native'; +import { ScrollView } from 'react-native-gesture-handler'; + +interface TestingScreenProps { + text: string; + setText: (text: string) => void; + children: React.ReactNode; +} + +export default function TestingScreen({ + text, + setText, + children, +}: TestingScreenProps) { + return ( + + + + {text} + + + + {children} + + + { + setText(''); + }}> + Reset + + + + ); +} + +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', + }, + resetButton: { + width: 120, + height: 40, + borderRadius: 20, + backgroundColor: 'royalblue', + + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/apps/common-app/src/e2e_screens/gestures/pan.tsx b/apps/common-app/src/e2e_screens/gestures/pan.tsx index f03c1a0b68..5f67e4fa85 100644 --- a/apps/common-app/src/e2e_screens/gestures/pan.tsx +++ b/apps/common-app/src/e2e_screens/gestures/pan.tsx @@ -1,10 +1,8 @@ import { useState } from 'react'; -import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { - GestureDetector, - ScrollView, - usePanGesture, -} from 'react-native-gesture-handler'; +import { StyleSheet, View } from 'react-native'; +import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; export default function PanScreen() { const [text, setText] = useState(''); @@ -33,67 +31,19 @@ export default function PanScreen() { }); return ( - - - - {text} - - - - - - - - - { - setText(''); - }}> - Reset - - - + + + + + ); } 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', - }, }); From 2839e4392acc818afaffffdf937f32a1a878c2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 May 2026 12:02:05 +0200 Subject: [PATCH 02/22] Add tap --- .../src/e2e_screens/gestures/Tap.tsx | 42 +++++++++++++++++++ apps/common-app/src/new_api/index.tsx | 8 +++- apps/expo-example/e2e/__tests__/tap.test.ts | 29 +++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 apps/common-app/src/e2e_screens/gestures/Tap.tsx create mode 100644 apps/expo-example/e2e/__tests__/tap.test.ts 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..ed4d7dbc28 --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/Tap.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { GestureDetector, useTapGesture } from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; + +export default function TapScreen() { + const [text, setText] = useState(''); + + const tapGesture = useTapGesture({ + onBegin: () => { + setText((prev) => prev + '1'); + }, + onActivate: () => { + setText((prev) => prev + '2'); + }, + onDeactivate: () => { + setText((prev) => prev + '4'); + }, + onFinalize: () => { + setText((prev) => prev + '5'); + }, + runOnJS: true, + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index f547882f63..cc89bde084 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -1,5 +1,6 @@ import type { ExamplesSection } from '../common'; -import panE2E from '../e2e_screens/gestures/pan'; +import PanE2E from '../e2e_screens/gestures/Pan'; +import TapE2E from '../e2e_screens/gestures/Tap'; import EmptyExample from '../empty'; import CameraExample from './complicated/camera'; import ChatHeadsExample from './complicated/chat_heads'; @@ -137,6 +138,9 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ }, { sectionTitle: 'E2E Tests', - data: [{ name: 'Pan Gesture', component: panE2E }], + data: [ + { name: 'Pan Gesture', component: PanE2E }, + { name: 'Tap Gesture', component: TapE2E }, + ], }, ]; diff --git a/apps/expo-example/e2e/__tests__/tap.test.ts b/apps/expo-example/e2e/__tests__/tap.test.ts new file mode 100644 index 0000000000..9bfafed8ec --- /dev/null +++ b/apps/expo-example/e2e/__tests__/tap.test.ts @@ -0,0 +1,29 @@ +// 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 tap gesture', () => { + beforeAll(async () => { + await navigateTo('Tap Gesture'); + }); + + const gestureBox = element(by.id('tap-box')); + const stateIndicator = element(by.id('state-indicator')); + const resetButton = element(by.id('reset')); + + beforeEach(async () => { + await resetButton.tap(); + }); + + test('Should register tap gesture', async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText('1245'); + }); + + test('Should register tap gesture on long press', async () => { + await gestureBox.longPress(1000); + await expect(stateIndicator).toHaveText('15'); + }); +}); From d7c1d12b9582726425fa4ba934bc18494deefff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 May 2026 13:05:42 +0200 Subject: [PATCH 03/22] Add long press test --- .../src/e2e_screens/gestures/LongPress.tsx | 45 +++++++++++++++++++ apps/common-app/src/new_api/index.tsx | 2 + .../e2e/__tests__/long_press.test.ts | 29 ++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 apps/common-app/src/e2e_screens/gestures/LongPress.tsx create mode 100644 apps/expo-example/e2e/__tests__/long_press.test.ts 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..37e5f6afd4 --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/LongPress.tsx @@ -0,0 +1,45 @@ +import { useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + GestureDetector, + useLongPressGesture, +} from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; + +export default function LongPressScreen() { + const [text, setText] = useState(''); + + const longPressGesture = useLongPressGesture({ + onBegin: () => { + setText((prev) => prev + '1'); + }, + onActivate: () => { + setText((prev) => prev + '2'); + }, + onDeactivate: () => { + setText((prev) => prev + '4'); + }, + onFinalize: () => { + setText((prev) => prev + '5'); + }, + runOnJS: true, + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index cc89bde084..5d71c502f7 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -1,6 +1,7 @@ import type { ExamplesSection } from '../common'; import PanE2E from '../e2e_screens/gestures/Pan'; import TapE2E from '../e2e_screens/gestures/Tap'; +import LongPressE2E from '../e2e_screens/gestures/LongPress'; import EmptyExample from '../empty'; import CameraExample from './complicated/camera'; import ChatHeadsExample from './complicated/chat_heads'; @@ -141,6 +142,7 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ data: [ { name: 'Pan Gesture', component: PanE2E }, { name: 'Tap Gesture', component: TapE2E }, + { name: 'Long Press Gesture', component: LongPressE2E }, ], }, ]; diff --git a/apps/expo-example/e2e/__tests__/long_press.test.ts b/apps/expo-example/e2e/__tests__/long_press.test.ts new file mode 100644 index 0000000000..1067354a41 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/long_press.test.ts @@ -0,0 +1,29 @@ +// 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 long press gesture', () => { + beforeAll(async () => { + await navigateTo('Long Press Gesture'); + }); + + const gestureBox = element(by.id('long-press-box')); + const stateIndicator = element(by.id('state-indicator')); + const resetButton = element(by.id('reset')); + + beforeEach(async () => { + await resetButton.tap(); + }); + + test('Should register long press gesture', async () => { + await gestureBox.longPress(1000); + await expect(stateIndicator).toHaveText('1245'); + }); + + test("Shouldn't register tap gesture", async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText('15'); + }); +}); From f5c8c3e5f37c94aafd809d57954c12385a10f2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 May 2026 14:52:26 +0200 Subject: [PATCH 04/22] Captial letter in filename --- apps/common-app/src/e2e_screens/gestures/{pan.tsx => Pan.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/common-app/src/e2e_screens/gestures/{pan.tsx => Pan.tsx} (100%) diff --git a/apps/common-app/src/e2e_screens/gestures/pan.tsx b/apps/common-app/src/e2e_screens/gestures/Pan.tsx similarity index 100% rename from apps/common-app/src/e2e_screens/gestures/pan.tsx rename to apps/common-app/src/e2e_screens/gestures/Pan.tsx From f96b7a46acf58d837689d1b80ebaaa38524649c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 May 2026 15:09:24 +0200 Subject: [PATCH 05/22] Fling --- .../src/e2e_screens/gestures/Fling.tsx | 42 +++++++++++++++++++ apps/common-app/src/new_api/index.tsx | 4 +- apps/expo-example/e2e/__tests__/fling.test.ts | 29 +++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 apps/common-app/src/e2e_screens/gestures/Fling.tsx create mode 100644 apps/expo-example/e2e/__tests__/fling.test.ts 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..615f93cd5b --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/Fling.tsx @@ -0,0 +1,42 @@ +import { useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { GestureDetector, useFlingGesture } from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; + +export default function FlingScreen() { + const [text, setText] = useState(''); + + const flingGesture = useFlingGesture({ + onBegin: () => { + setText((prev) => prev + '1'); + }, + onActivate: () => { + setText((prev) => prev + '2'); + }, + onDeactivate: () => { + setText((prev) => prev + '4'); + }, + onFinalize: () => { + setText((prev) => prev + '5'); + }, + runOnJS: true, + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index 5d71c502f7..097461edd3 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -1,7 +1,8 @@ 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 TapE2E from '../e2e_screens/gestures/Tap'; -import LongPressE2E from '../e2e_screens/gestures/LongPress'; import EmptyExample from '../empty'; import CameraExample from './complicated/camera'; import ChatHeadsExample from './complicated/chat_heads'; @@ -143,6 +144,7 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ { name: 'Pan Gesture', component: PanE2E }, { name: 'Tap Gesture', component: TapE2E }, { name: 'Long Press Gesture', component: LongPressE2E }, + { name: 'Fling Gesture', component: FlingE2E }, ], }, ]; diff --git a/apps/expo-example/e2e/__tests__/fling.test.ts b/apps/expo-example/e2e/__tests__/fling.test.ts new file mode 100644 index 0000000000..d3b69dc0fc --- /dev/null +++ b/apps/expo-example/e2e/__tests__/fling.test.ts @@ -0,0 +1,29 @@ +// 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 fling gesture', () => { + beforeAll(async () => { + await navigateTo('Fling Gesture'); + }); + + const gestureBox = element(by.id('fling-box')); + const stateIndicator = element(by.id('state-indicator')); + const resetButton = element(by.id('reset')); + + beforeEach(async () => { + await resetButton.tap(); + }); + + test('Should register fling gesture', async () => { + await gestureBox.swipe('right', 'fast'); + await expect(stateIndicator).toHaveText('1245'); + }); + + test("Shouldn't register fling gesture", async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText('15'); + }); +}); From 6cacbf8f575bf7b01f339097d786b9035b31efea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 May 2026 15:11:32 +0200 Subject: [PATCH 06/22] Add [E2E] prefix --- apps/common-app/src/new_api/index.tsx | 8 ++++---- apps/expo-example/e2e/__tests__/fling.test.ts | 2 +- apps/expo-example/e2e/__tests__/long_press.test.ts | 2 +- apps/expo-example/e2e/__tests__/pan.test.ts | 2 +- apps/expo-example/e2e/__tests__/tap.test.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index 097461edd3..d1b2291b74 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -141,10 +141,10 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ { sectionTitle: 'E2E Tests', data: [ - { name: 'Pan Gesture', component: PanE2E }, - { name: 'Tap Gesture', component: TapE2E }, - { name: 'Long Press Gesture', component: LongPressE2E }, - { name: 'Fling Gesture', component: FlingE2E }, + { name: '[E2E] Pan', component: PanE2E }, + { name: '[E2E] Tap', component: TapE2E }, + { name: '[E2E] LongPress', component: LongPressE2E }, + { name: '[E2E] Fling', component: FlingE2E }, ], }, ]; diff --git a/apps/expo-example/e2e/__tests__/fling.test.ts b/apps/expo-example/e2e/__tests__/fling.test.ts index d3b69dc0fc..8e24a84f63 100644 --- a/apps/expo-example/e2e/__tests__/fling.test.ts +++ b/apps/expo-example/e2e/__tests__/fling.test.ts @@ -6,7 +6,7 @@ import { navigateTo } from '../utils'; describe('test fling gesture', () => { beforeAll(async () => { - await navigateTo('Fling Gesture'); + await navigateTo('[E2E] Fling'); }); const gestureBox = element(by.id('fling-box')); diff --git a/apps/expo-example/e2e/__tests__/long_press.test.ts b/apps/expo-example/e2e/__tests__/long_press.test.ts index 1067354a41..0b60a84262 100644 --- a/apps/expo-example/e2e/__tests__/long_press.test.ts +++ b/apps/expo-example/e2e/__tests__/long_press.test.ts @@ -6,7 +6,7 @@ import { navigateTo } from '../utils'; describe('test long press gesture', () => { beforeAll(async () => { - await navigateTo('Long Press Gesture'); + await navigateTo('[E2E] LongPress'); }); const gestureBox = element(by.id('long-press-box')); diff --git a/apps/expo-example/e2e/__tests__/pan.test.ts b/apps/expo-example/e2e/__tests__/pan.test.ts index fd99138938..b8bd365bc1 100644 --- a/apps/expo-example/e2e/__tests__/pan.test.ts +++ b/apps/expo-example/e2e/__tests__/pan.test.ts @@ -6,7 +6,7 @@ import { navigateTo } from '../utils'; describe('test pan gesture', () => { beforeAll(async () => { - await navigateTo('Pan Gesture'); + await navigateTo('[E2E] Pan'); }); const gestureBox = element(by.id('pan-box')); diff --git a/apps/expo-example/e2e/__tests__/tap.test.ts b/apps/expo-example/e2e/__tests__/tap.test.ts index 9bfafed8ec..c5653b8e2f 100644 --- a/apps/expo-example/e2e/__tests__/tap.test.ts +++ b/apps/expo-example/e2e/__tests__/tap.test.ts @@ -6,7 +6,7 @@ import { navigateTo } from '../utils'; describe('test tap gesture', () => { beforeAll(async () => { - await navigateTo('Tap Gesture'); + await navigateTo('[E2E] Tap'); }); const gestureBox = element(by.id('tap-box')); From f5c900bf2ff7a734eee2afb2b0ddb8a088f9ab0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 May 2026 15:46:16 +0200 Subject: [PATCH 07/22] Keep routes in variables --- apps/common-app/src/e2e_screens/screenNames.ts | 6 ++++++ apps/common-app/src/new_api/index.tsx | 9 +++++---- apps/expo-example/e2e/__tests__/fling.test.ts | 3 ++- apps/expo-example/e2e/__tests__/long_press.test.ts | 3 ++- apps/expo-example/e2e/__tests__/pan.test.ts | 3 ++- apps/expo-example/e2e/__tests__/tap.test.ts | 3 ++- 6 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 apps/common-app/src/e2e_screens/screenNames.ts diff --git a/apps/common-app/src/e2e_screens/screenNames.ts b/apps/common-app/src/e2e_screens/screenNames.ts new file mode 100644 index 0000000000..be071daa26 --- /dev/null +++ b/apps/common-app/src/e2e_screens/screenNames.ts @@ -0,0 +1,6 @@ +export const TestScreens = { + Pan: '[E2E] Pan', + Tap: '[E2E] Tap', + LongPress: '[E2E] LongPress', + Fling: '[E2E] Fling', +}; diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index d1b2291b74..7e98ac40f6 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -3,6 +3,7 @@ import FlingE2E from '../e2e_screens/gestures/Fling'; import LongPressE2E from '../e2e_screens/gestures/LongPress'; import PanE2E from '../e2e_screens/gestures/Pan'; import TapE2E from '../e2e_screens/gestures/Tap'; +import { TestScreens } from '../e2e_screens/screenNames'; import EmptyExample from '../empty'; import CameraExample from './complicated/camera'; import ChatHeadsExample from './complicated/chat_heads'; @@ -141,10 +142,10 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ { sectionTitle: 'E2E Tests', data: [ - { name: '[E2E] Pan', component: PanE2E }, - { name: '[E2E] Tap', component: TapE2E }, - { name: '[E2E] LongPress', component: LongPressE2E }, - { name: '[E2E] Fling', component: FlingE2E }, + { name: TestScreens.Pan, component: PanE2E }, + { name: TestScreens.Tap, component: TapE2E }, + { name: TestScreens.LongPress, component: LongPressE2E }, + { name: TestScreens.Fling, component: FlingE2E }, ], }, ]; diff --git a/apps/expo-example/e2e/__tests__/fling.test.ts b/apps/expo-example/e2e/__tests__/fling.test.ts index 8e24a84f63..33125a24c9 100644 --- a/apps/expo-example/e2e/__tests__/fling.test.ts +++ b/apps/expo-example/e2e/__tests__/fling.test.ts @@ -1,12 +1,13 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; import { by, element, expect } from 'detox'; import { navigateTo } from '../utils'; describe('test fling gesture', () => { beforeAll(async () => { - await navigateTo('[E2E] Fling'); + await navigateTo(TestScreens.Fling); }); const gestureBox = element(by.id('fling-box')); diff --git a/apps/expo-example/e2e/__tests__/long_press.test.ts b/apps/expo-example/e2e/__tests__/long_press.test.ts index 0b60a84262..b3ff184d0f 100644 --- a/apps/expo-example/e2e/__tests__/long_press.test.ts +++ b/apps/expo-example/e2e/__tests__/long_press.test.ts @@ -1,12 +1,13 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; import { by, element, expect } from 'detox'; import { navigateTo } from '../utils'; describe('test long press gesture', () => { beforeAll(async () => { - await navigateTo('[E2E] LongPress'); + await navigateTo(TestScreens.LongPress); }); const gestureBox = element(by.id('long-press-box')); diff --git a/apps/expo-example/e2e/__tests__/pan.test.ts b/apps/expo-example/e2e/__tests__/pan.test.ts index b8bd365bc1..07c94fdc8f 100644 --- a/apps/expo-example/e2e/__tests__/pan.test.ts +++ b/apps/expo-example/e2e/__tests__/pan.test.ts @@ -1,12 +1,13 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; import { by, element, expect } from 'detox'; import { navigateTo } from '../utils'; describe('test pan gesture', () => { beforeAll(async () => { - await navigateTo('[E2E] Pan'); + await navigateTo(TestScreens.Pan); }); const gestureBox = element(by.id('pan-box')); diff --git a/apps/expo-example/e2e/__tests__/tap.test.ts b/apps/expo-example/e2e/__tests__/tap.test.ts index c5653b8e2f..36cb68f044 100644 --- a/apps/expo-example/e2e/__tests__/tap.test.ts +++ b/apps/expo-example/e2e/__tests__/tap.test.ts @@ -1,12 +1,13 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; import { by, element, expect } from 'detox'; import { navigateTo } from '../utils'; describe('test tap gesture', () => { beforeAll(async () => { - await navigateTo('[E2E] Tap'); + await navigateTo(TestScreens.Tap); }); const gestureBox = element(by.id('tap-box')); From 921f7fffd6d1a72cf93a08fe19608edf5856c211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 May 2026 16:13:40 +0200 Subject: [PATCH 08/22] Add pinch --- .../src/e2e_screens/gestures/Pinch.tsx | 49 +++++++++++++++++++ .../common-app/src/e2e_screens/screenNames.ts | 1 + apps/common-app/src/new_api/index.tsx | 2 + apps/expo-example/e2e/__tests__/pinch.test.ts | 30 ++++++++++++ 4 files changed, 82 insertions(+) create mode 100644 apps/common-app/src/e2e_screens/gestures/Pinch.tsx create mode 100644 apps/expo-example/e2e/__tests__/pinch.test.ts 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..c2285c24c9 --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/Pinch.tsx @@ -0,0 +1,49 @@ +import { useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { GestureDetector, usePinchGesture } from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; + +export default function PinchScreen() { + const [text, setText] = useState(''); + + const pinchGesture = usePinchGesture({ + onBegin: () => { + setText((prev) => prev + '1'); + }, + onActivate: () => { + setText((prev) => prev + '2'); + }, + onUpdate: () => { + // Skip subsequent updates + if (text[text.length - 1] === '3') { + return; + } + setText((prev) => prev + '3'); + }, + onDeactivate: () => { + setText((prev) => prev + '4'); + }, + onFinalize: () => { + setText((prev) => prev + '5'); + }, + runOnJS: true, + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/screenNames.ts b/apps/common-app/src/e2e_screens/screenNames.ts index be071daa26..4c46906c59 100644 --- a/apps/common-app/src/e2e_screens/screenNames.ts +++ b/apps/common-app/src/e2e_screens/screenNames.ts @@ -3,4 +3,5 @@ export const TestScreens = { Tap: '[E2E] Tap', LongPress: '[E2E] LongPress', Fling: '[E2E] Fling', + Pinch: '[E2E] Pinch', }; diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index 7e98ac40f6..708a7011cf 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -2,6 +2,7 @@ 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 TapE2E from '../e2e_screens/gestures/Tap'; import { TestScreens } from '../e2e_screens/screenNames'; import EmptyExample from '../empty'; @@ -143,6 +144,7 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ sectionTitle: 'E2E Tests', data: [ { name: TestScreens.Pan, component: PanE2E }, + { name: TestScreens.Pinch, component: PinchE2E }, { name: TestScreens.Tap, component: TapE2E }, { name: TestScreens.LongPress, component: LongPressE2E }, { name: TestScreens.Fling, component: FlingE2E }, diff --git a/apps/expo-example/e2e/__tests__/pinch.test.ts b/apps/expo-example/e2e/__tests__/pinch.test.ts new file mode 100644 index 0000000000..2264ae6051 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/pinch.test.ts @@ -0,0 +1,30 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { by, element, expect } from 'detox'; + +import { navigateTo } from '../utils'; + +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 resetButton = element(by.id('reset')); + + beforeEach(async () => { + await resetButton.tap(); + }); + + test('Should register pinch gesture on pinch', async () => { + await gestureBox.pinch(1.5, 'fast'); + await expect(stateIndicator).toHaveText('12345'); + }); + + test('Shouldn`t register a pinch gesture on tap', async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText(''); + }); +}); From 252998e289438631687873ce2c7a2fe2c885be56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Thu, 21 May 2026 16:25:51 +0200 Subject: [PATCH 09/22] Rotation with argent --- .../src/e2e_screens/gestures/Rotation.tsx | 52 ++++++++++++ .../common-app/src/e2e_screens/screenNames.ts | 1 + apps/common-app/src/new_api/index.tsx | 2 + .../e2e/__tests__/rotation.test.ts | 80 +++++++++++++++++++ 4 files changed, 135 insertions(+) create mode 100644 apps/common-app/src/e2e_screens/gestures/Rotation.tsx create mode 100644 apps/expo-example/e2e/__tests__/rotation.test.ts 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..7e00dc4d81 --- /dev/null +++ b/apps/common-app/src/e2e_screens/gestures/Rotation.tsx @@ -0,0 +1,52 @@ +import { useState } from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + GestureDetector, + useRotationGesture, +} from 'react-native-gesture-handler'; + +import TestingScreen from '../TestingScreen'; + +export default function RotationScreen() { + const [text, setText] = useState(''); + + const rotationGesture = useRotationGesture({ + onBegin: () => { + setText((prev) => prev + '1'); + }, + onActivate: () => { + setText((prev) => prev + '2'); + }, + onUpdate: () => { + // Skip subsequent updates + if (text[text.length - 1] === '3') { + return; + } + setText((prev) => prev + '3'); + }, + onDeactivate: () => { + setText((prev) => prev + '4'); + }, + onFinalize: () => { + setText((prev) => prev + '5'); + }, + runOnJS: true, + }); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + gestureBox: { + width: 120, + height: 120, + borderRadius: 20, + backgroundColor: '#4ecdc4', + }, +}); diff --git a/apps/common-app/src/e2e_screens/screenNames.ts b/apps/common-app/src/e2e_screens/screenNames.ts index 4c46906c59..8c09d536a5 100644 --- a/apps/common-app/src/e2e_screens/screenNames.ts +++ b/apps/common-app/src/e2e_screens/screenNames.ts @@ -4,4 +4,5 @@ export const TestScreens = { LongPress: '[E2E] LongPress', Fling: '[E2E] Fling', Pinch: '[E2E] Pinch', + Rotation: '[E2E] Rotation', }; diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index 708a7011cf..46c9f79845 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -3,6 +3,7 @@ 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 { TestScreens } from '../e2e_screens/screenNames'; import EmptyExample from '../empty'; @@ -145,6 +146,7 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ 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 }, diff --git a/apps/expo-example/e2e/__tests__/rotation.test.ts b/apps/expo-example/e2e/__tests__/rotation.test.ts new file mode 100644 index 0000000000..af596daa2f --- /dev/null +++ b/apps/expo-example/e2e/__tests__/rotation.test.ts @@ -0,0 +1,80 @@ +import { spawn } from 'node:child_process'; + +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { by, element, expect } from 'detox'; + +import { navigateTo } 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); + }); + }); +} + +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 resetButton = element(by.id('reset')); + + beforeEach(async () => { + await resetButton.tap(); + }); + + 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 expect(stateIndicator).toHaveText('12345'); + }); + + test('Shouldn`t register a rotation gesture on tap', async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText(''); + }); +}); From 801f957a1b8e28a0bb867f27c66b488e8ae120da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 22 May 2026 10:21:57 +0200 Subject: [PATCH 10/22] Update setup-detox-script --- apps/expo-example/setup-detox-android.js | 43 +++++++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/apps/expo-example/setup-detox-android.js b/apps/expo-example/setup-detox-android.js index 8caf60497b..071c8c64a2 100644 --- a/apps/expo-example/setup-detox-android.js +++ b/apps/expo-example/setup-detox-android.js @@ -1,7 +1,6 @@ #!/usr/bin/env node /** * Patches the Android project after `expo prebuild` to add Detox e2e test support. - * Run with: node scripts/setup-detox-android.js */ // eslint-disable-next-line import-x/no-commonjs, @typescript-eslint/no-var-requires @@ -11,6 +10,42 @@ const { join } = require('path'); const androidDir = join(__dirname, 'android'); +// Derive android package from build.gradle written by expo prebuild. +// This avoids hardcoding the package and keeps things in sync with app.config.*. +const appBuildGradleContent = readFileSync( + join(androidDir, 'app', 'build.gradle'), + 'utf8' +); +const packageMatch = appBuildGradleContent.match(/applicationId\s+['"]([^'"]+)['"]/); +if (!packageMatch) { + console.error( + 'ERROR: Could not determine android package from android/app/build.gradle.\n' + + 'Make sure you have run `expo prebuild` before running this script.' + ); + process.exit(1); +} +const androidPackage = packageMatch[1]; // e.g. "com.example.ExpoExample" +const packagePath = androidPackage.replace(/\./g, '/'); // e.g. "com/example/ExpoExample" + +// Validate that the main source package directory exists (sanity check after prebuild). +const mainPackageDir = join( + androidDir, + 'app', + 'src', + 'main', + 'java', + packagePath +); +if (!existsSync(mainPackageDir)) { + console.error( + `ERROR: Expected package directory does not exist: ${mainPackageDir}\n` + + 'Make sure you have run `expo prebuild` before running this script.' + ); + process.exit(1); +} + +console.log(`Detected android package: ${androidPackage}`); + function patchFile(filePath, patches) { let content = readFileSync(filePath, 'utf8'); let changed = false; @@ -99,9 +134,7 @@ const detoxTestDir = join( 'src', 'androidTest', 'java', - 'com', - 'example', - 'ExpoExample' + packagePath ); const detoxTestPath = join(detoxTestDir, 'DetoxTest.kt'); if (existsSync(detoxTestPath)) { @@ -111,7 +144,7 @@ if (existsSync(detoxTestPath)) { writeFileSync( detoxTestPath, [ - 'package com.example.ExpoExample', + `package ${androidPackage}`, '', 'import androidx.test.ext.junit.runners.AndroidJUnit4', 'import androidx.test.filters.LargeTest', From 9b74266c5165daa58f76cb35f5220965a6bc3fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 22 May 2026 10:33:34 +0200 Subject: [PATCH 11/22] Update CI file --- .github/workflows/e2e.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index bcaab603b4..3e17eb9bb0 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,9 +1,6 @@ name: E2E Test on: - pull_request: - paths: - - packages/react-native-gesture-handler/** push: branches: - main @@ -40,9 +37,6 @@ jobs: - name: Setup Detox run: brew tap wix/brew && brew install applesimutils && npm install -g detox-cli - # - name: Install argent - # run: npm install -g @swmansion/argent - - name: Build for iOS working-directory: ${{ env.WORKING_DIRECTORY }} run: yarn build-detox-ios @@ -87,7 +81,7 @@ jobs: working-directory: ${{ env.WORKING_DIRECTORY }} run: yarn build-detox-android - - name: run tests + - name: Run tests on Android uses: reactivecircus/android-emulator-runner@v2 with: api-level: 33 From c4a8ac6093311d420a887d4a2acd9a09af71c115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 22 May 2026 10:36:21 +0200 Subject: [PATCH 12/22] New line --- apps/expo-example/e2e/jest.config.js | 2 +- apps/expo-example/e2e/tsconfig.json | 24 +++++++++--------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/apps/expo-example/e2e/jest.config.js b/apps/expo-example/e2e/jest.config.js index c0eb6718c6..73343404e2 100644 --- a/apps/expo-example/e2e/jest.config.js +++ b/apps/expo-example/e2e/jest.config.js @@ -12,4 +12,4 @@ module.exports = { transform: { '^.+\\.(js|ts|tsx)$': ['babel-jest', { configFile: './babel.config.js' }], }, -}; \ No newline at end of file +}; diff --git a/apps/expo-example/e2e/tsconfig.json b/apps/expo-example/e2e/tsconfig.json index 59587b61a8..d2da3ed7df 100644 --- a/apps/expo-example/e2e/tsconfig.json +++ b/apps/expo-example/e2e/tsconfig.json @@ -1,16 +1,10 @@ { - "extends": "../tsconfig.json", - "compilerOptions": { - "module": "commonjs", - "target": "es6", - "types": [ - "jest", - "detox", - "node" - ], - "skipLibCheck": true - }, - "include": [ - "**/*.ts" - ] -} \ No newline at end of file + "extends": "../tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "types": ["jest", "detox", "node"], + "skipLibCheck": true + }, + "include": ["**/*.ts"] +} From 6d3726f9faed382ee66f2884935172fc3e78e987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 22 May 2026 11:06:12 +0200 Subject: [PATCH 13/22] Format --- apps/expo-example/.detoxrc.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/expo-example/.detoxrc.js b/apps/expo-example/.detoxrc.js index 8cd210bf53..bdb502cc4c 100644 --- a/apps/expo-example/.detoxrc.js +++ b/apps/expo-example/.detoxrc.js @@ -34,7 +34,8 @@ module.exports = { 'android.release': { type: 'android.apk', binaryPath: 'android/app/build/outputs/apk/release/app-release.apk', - testBinaryPath: 'android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk', + testBinaryPath: + 'android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk', build: 'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release', }, From 339d0063ddb1d06257b8bf206a34ba88e1d6ab3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Fri, 22 May 2026 11:06:32 +0200 Subject: [PATCH 14/22] Remove new-app-screen --- apps/expo-example/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/expo-example/package.json b/apps/expo-example/package.json index 6e40e9ec01..c38a764267 100644 --- a/apps/expo-example/package.json +++ b/apps/expo-example/package.json @@ -19,7 +19,6 @@ "dependencies": { "@expo/metro-runtime": "~56.0.10", "@react-native-async-storage/async-storage": "2.2.0", - "@react-native/new-app-screen": "0.85.2", "@react-navigation/elements": "^2.3.8", "@react-navigation/native": "^7.1.6", "@react-navigation/native-stack": "^7.14.12", From 8917512492cc1ffd0803489bde4ed50bfdad6925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 25 May 2026 18:13:30 +0200 Subject: [PATCH 15/22] Merge tests --- apps/expo-example/e2e/__tests__/all.test.ts | 13 ++++ apps/expo-example/e2e/__tests__/fling.test.ts | 30 ------- .../e2e/__tests__/gestures/fling.test.ts | 3 + .../e2e/__tests__/gestures/long_press.test.ts | 3 + .../e2e/__tests__/gestures/pan.test.ts | 3 + .../e2e/__tests__/gestures/pinch.test.ts | 3 + .../e2e/__tests__/gestures/rotation.test.ts | 3 + .../e2e/__tests__/gestures/tap.test.ts | 3 + .../e2e/__tests__/long_press.test.ts | 30 ------- apps/expo-example/e2e/__tests__/pan.test.ts | 30 ------- apps/expo-example/e2e/__tests__/pinch.test.ts | 30 ------- apps/expo-example/e2e/__tests__/tap.test.ts | 30 ------- apps/expo-example/e2e/suites/fling.ts | 32 ++++++++ apps/expo-example/e2e/suites/long_press.ts | 32 ++++++++ apps/expo-example/e2e/suites/pan.ts | 32 ++++++++ apps/expo-example/e2e/suites/pinch.ts | 78 +++++++++++++++++++ .../rotation.test.ts => suites/rotation.ts} | 52 +++++++------ apps/expo-example/e2e/suites/tap.ts | 32 ++++++++ 18 files changed, 264 insertions(+), 175 deletions(-) create mode 100644 apps/expo-example/e2e/__tests__/all.test.ts delete mode 100644 apps/expo-example/e2e/__tests__/fling.test.ts create mode 100644 apps/expo-example/e2e/__tests__/gestures/fling.test.ts create mode 100644 apps/expo-example/e2e/__tests__/gestures/long_press.test.ts create mode 100644 apps/expo-example/e2e/__tests__/gestures/pan.test.ts create mode 100644 apps/expo-example/e2e/__tests__/gestures/pinch.test.ts create mode 100644 apps/expo-example/e2e/__tests__/gestures/rotation.test.ts create mode 100644 apps/expo-example/e2e/__tests__/gestures/tap.test.ts delete mode 100644 apps/expo-example/e2e/__tests__/long_press.test.ts delete mode 100644 apps/expo-example/e2e/__tests__/pan.test.ts delete mode 100644 apps/expo-example/e2e/__tests__/pinch.test.ts delete mode 100644 apps/expo-example/e2e/__tests__/tap.test.ts create mode 100644 apps/expo-example/e2e/suites/fling.ts create mode 100644 apps/expo-example/e2e/suites/long_press.ts create mode 100644 apps/expo-example/e2e/suites/pan.ts create mode 100644 apps/expo-example/e2e/suites/pinch.ts rename apps/expo-example/e2e/{__tests__/rotation.test.ts => suites/rotation.ts} (55%) create mode 100644 apps/expo-example/e2e/suites/tap.ts 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..f1c581a5b6 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/all.test.ts @@ -0,0 +1,13 @@ +import { flingTests } from '../suites/fling'; +import { longPressTests } from '../suites/long_press'; +import { panTests } from '../suites/pan'; +import { pinchTests } from '../suites/pinch'; +import { rotationTests } from '../suites/rotation'; +import { tapTests } from '../suites/tap'; + +tapTests(); +pinchTests(); +flingTests(); +longPressTests(); +rotationTests(); +panTests(); diff --git a/apps/expo-example/e2e/__tests__/fling.test.ts b/apps/expo-example/e2e/__tests__/fling.test.ts deleted file mode 100644 index 33125a24c9..0000000000 --- a/apps/expo-example/e2e/__tests__/fling.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -// eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; -import { by, element, expect } from 'detox'; - -import { navigateTo } from '../utils'; - -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 resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); - - test('Should register fling gesture', async () => { - await gestureBox.swipe('right', 'fast'); - await expect(stateIndicator).toHaveText('1245'); - }); - - test("Shouldn't register fling gesture", async () => { - await gestureBox.tap(); - await expect(stateIndicator).toHaveText('15'); - }); -}); 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..f2e2820121 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/fling.test.ts @@ -0,0 +1,3 @@ +import { flingTests } from '../../suites/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..53b3b6be64 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/long_press.test.ts @@ -0,0 +1,3 @@ +import { longPressTests } from '../../suites/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..ca1f68d2ed --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/pan.test.ts @@ -0,0 +1,3 @@ +import { panTests } from '../../suites/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..1fc2ef3ae6 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/pinch.test.ts @@ -0,0 +1,3 @@ +import { pinchTests } from '../../suites/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..2f87ca0c2f --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/rotation.test.ts @@ -0,0 +1,3 @@ +import { rotationTests } from '../../suites/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..457dba983f --- /dev/null +++ b/apps/expo-example/e2e/__tests__/gestures/tap.test.ts @@ -0,0 +1,3 @@ +import { tapTests } from '../../suites/tap'; + +tapTests(); diff --git a/apps/expo-example/e2e/__tests__/long_press.test.ts b/apps/expo-example/e2e/__tests__/long_press.test.ts deleted file mode 100644 index b3ff184d0f..0000000000 --- a/apps/expo-example/e2e/__tests__/long_press.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -// eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; -import { by, element, expect } from 'detox'; - -import { navigateTo } from '../utils'; - -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 resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); - - test('Should register long press gesture', async () => { - await gestureBox.longPress(1000); - await expect(stateIndicator).toHaveText('1245'); - }); - - test("Shouldn't register tap gesture", async () => { - await gestureBox.tap(); - await expect(stateIndicator).toHaveText('15'); - }); -}); 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 81f241dd56..0000000000 --- a/apps/expo-example/e2e/__tests__/pan.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -// eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; -import { by, element, expect } from 'detox'; - -import { navigateTo } from '../utils'; - -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 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__/pinch.test.ts b/apps/expo-example/e2e/__tests__/pinch.test.ts deleted file mode 100644 index 2264ae6051..0000000000 --- a/apps/expo-example/e2e/__tests__/pinch.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -// eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; -import { by, element, expect } from 'detox'; - -import { navigateTo } from '../utils'; - -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 resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); - - test('Should register pinch gesture on pinch', async () => { - await gestureBox.pinch(1.5, 'fast'); - await expect(stateIndicator).toHaveText('12345'); - }); - - test('Shouldn`t register a pinch gesture on tap', async () => { - await gestureBox.tap(); - await expect(stateIndicator).toHaveText(''); - }); -}); diff --git a/apps/expo-example/e2e/__tests__/tap.test.ts b/apps/expo-example/e2e/__tests__/tap.test.ts deleted file mode 100644 index 36cb68f044..0000000000 --- a/apps/expo-example/e2e/__tests__/tap.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -// eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; -import { by, element, expect } from 'detox'; - -import { navigateTo } from '../utils'; - -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 resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); - - test('Should register tap gesture', async () => { - await gestureBox.tap(); - await expect(stateIndicator).toHaveText('1245'); - }); - - test('Should register tap gesture on long press', async () => { - await gestureBox.longPress(1000); - await expect(stateIndicator).toHaveText('15'); - }); -}); diff --git a/apps/expo-example/e2e/suites/fling.ts b/apps/expo-example/e2e/suites/fling.ts new file mode 100644 index 0000000000..f5b8d57eb8 --- /dev/null +++ b/apps/expo-example/e2e/suites/fling.ts @@ -0,0 +1,32 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { by, element, expect } from 'detox'; + +import { navigateTo } 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 resetButton = element(by.id('reset')); + + beforeEach(async () => { + await resetButton.tap(); + }); + + test('Should register fling gesture', async () => { + await gestureBox.swipe('right', 'fast'); + await expect(stateIndicator).toHaveText('1245'); + }); + + test("Shouldn't register fling gesture", async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText('15'); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/long_press.ts b/apps/expo-example/e2e/suites/long_press.ts new file mode 100644 index 0000000000..acd1162f31 --- /dev/null +++ b/apps/expo-example/e2e/suites/long_press.ts @@ -0,0 +1,32 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { by, element, expect } from 'detox'; + +import { navigateTo } 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 resetButton = element(by.id('reset')); + + beforeEach(async () => { + await resetButton.tap(); + }); + + test('Should register long press gesture', async () => { + await gestureBox.longPress(1000); + await expect(stateIndicator).toHaveText('1245'); + }); + + test("Shouldn't register tap gesture", async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText('15'); + }); + }); +} diff --git a/apps/expo-example/e2e/suites/pan.ts b/apps/expo-example/e2e/suites/pan.ts new file mode 100644 index 0000000000..966cb921b0 --- /dev/null +++ b/apps/expo-example/e2e/suites/pan.ts @@ -0,0 +1,32 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { by, element, expect } from 'detox'; + +import { navigateTo } 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 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/suites/pinch.ts b/apps/expo-example/e2e/suites/pinch.ts new file mode 100644 index 0000000000..d705227306 --- /dev/null +++ b/apps/expo-example/e2e/suites/pinch.ts @@ -0,0 +1,78 @@ +import { spawn } from 'node:child_process'; + +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { by, element, expect } from 'detox'; + +import { navigateTo } 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 resetButton = element(by.id('reset')); + + beforeEach(async () => { + await resetButton.tap(); + }); + + 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 expect(stateIndicator).toHaveText('12345'); + }); + + test('Shouldn`t register a pinch gesture on tap', async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText(''); + }); + }); +} diff --git a/apps/expo-example/e2e/__tests__/rotation.test.ts b/apps/expo-example/e2e/suites/rotation.ts similarity index 55% rename from apps/expo-example/e2e/__tests__/rotation.test.ts rename to apps/expo-example/e2e/suites/rotation.ts index af596daa2f..ceab87da3c 100644 --- a/apps/expo-example/e2e/__tests__/rotation.test.ts +++ b/apps/expo-example/e2e/suites/rotation.ts @@ -46,35 +46,37 @@ function argentRotate(udid: string, ra: RotationArgs): Promise { }); } -describe('test rotation gesture', () => { - beforeAll(async () => { - await navigateTo(TestScreens.Rotation); - }); +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 resetButton = element(by.id('reset')); + const gestureBox = element(by.id('rotation-box')); + const stateIndicator = element(by.id('state-indicator')); + const resetButton = element(by.id('reset')); - beforeEach(async () => { - await resetButton.tap(); - }); + beforeEach(async () => { + await resetButton.tap(); + }); - test('Should register rotation gesture on rotation', async () => { - const udid = device.id; + 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 argentRotate(udid, { + centerX: '0.5', + centerY: '0.55', + startAngle: '0', + endAngle: '90', + radius: '0.05', + }); - await expect(stateIndicator).toHaveText('12345'); - }); + await expect(stateIndicator).toHaveText('12345'); + }); - test('Shouldn`t register a rotation gesture on tap', async () => { - await gestureBox.tap(); - await expect(stateIndicator).toHaveText(''); + test('Shouldn`t register a rotation gesture on tap', async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText(''); + }); }); -}); +} diff --git a/apps/expo-example/e2e/suites/tap.ts b/apps/expo-example/e2e/suites/tap.ts new file mode 100644 index 0000000000..a3c9c50954 --- /dev/null +++ b/apps/expo-example/e2e/suites/tap.ts @@ -0,0 +1,32 @@ +// eslint-disable-next-line import-x/no-extraneous-dependencies +import { beforeAll, beforeEach, describe } from '@jest/globals'; +import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { by, element, expect } from 'detox'; + +import { navigateTo } 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 resetButton = element(by.id('reset')); + + beforeEach(async () => { + await resetButton.tap(); + }); + + test('Should register tap gesture', async () => { + await gestureBox.tap(); + await expect(stateIndicator).toHaveText('1245'); + }); + + test('Should register tap gesture on long press', async () => { + await gestureBox.longPress(1000); + await expect(stateIndicator).toHaveText('15'); + }); + }); +} From 281bc50ec732e75f13ce999b8ee218c66ab534a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 May 2026 10:31:15 +0200 Subject: [PATCH 16/22] Update script in package.json --- apps/expo-example/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/expo-example/package.json b/apps/expo-example/package.json index 4109d367d7..18866c50a8 100644 --- a/apps/expo-example/package.json +++ b/apps/expo-example/package.json @@ -13,8 +13,8 @@ "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:android": "detox test -c android.emu.release e2e/__tests__/all.test.ts" }, "dependencies": { "@expo/metro-runtime": "~56.0.10", From 2051e8dc80eb0ff02e3ab592fb957f3af96087c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 May 2026 10:37:09 +0200 Subject: [PATCH 17/22] Add argent to CI --- .github/workflows/e2e.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 5963dfc531..0c3c555210 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -38,6 +38,9 @@ jobs: - name: Install dependencies run: yarn --immutable + - name: Install Argent + run: npm install -g @swmansion/argent + - name: Setup Detox run: brew tap wix/brew && brew install applesimutils && npm install -g detox-cli @@ -78,6 +81,9 @@ jobs: - name: Install dependencies run: yarn --immutable + - name: Install Argent + run: npm install -g @swmansion/argent + - name: Setup Detox run: npm install -g detox-cli From 18929e0cec270ee562d69c3e90f1f0f757adaeda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 May 2026 11:58:18 +0200 Subject: [PATCH 18/22] Use set --- .../src/e2e_screens/TestingScreen.tsx | 27 +++--- .../src/e2e_screens/gestures/Fling.tsx | 19 +++-- .../src/e2e_screens/gestures/LongPress.tsx | 19 +++-- .../src/e2e_screens/gestures/Pan.tsx | 25 +++--- .../src/e2e_screens/gestures/Pinch.tsx | 25 +++--- .../src/e2e_screens/gestures/Rotation.tsx | 25 +++--- .../src/e2e_screens/gestures/Tap.tsx | 19 +++-- .../src/e2e_screens/relations/Competing.tsx | 82 +++++++++++++++++++ .../e2e_screens/{screenNames.ts => utils.ts} | 9 ++ apps/common-app/src/new_api/index.tsx | 4 +- apps/expo-example/e2e/suites/fling.ts | 19 ++--- apps/expo-example/e2e/suites/long_press.ts | 19 ++--- apps/expo-example/e2e/suites/pan.ts | 19 ++--- apps/expo-example/e2e/suites/pinch.ts | 19 ++--- apps/expo-example/e2e/suites/rotation.ts | 19 ++--- apps/expo-example/e2e/suites/tap.ts | 19 ++--- apps/expo-example/e2e/utils.ts | 11 +++ 17 files changed, 254 insertions(+), 125 deletions(-) create mode 100644 apps/common-app/src/e2e_screens/relations/Competing.tsx rename apps/common-app/src/e2e_screens/{screenNames.ts => utils.ts} (53%) diff --git a/apps/common-app/src/e2e_screens/TestingScreen.tsx b/apps/common-app/src/e2e_screens/TestingScreen.tsx index 63f3ee0efa..02a80aff1a 100644 --- a/apps/common-app/src/e2e_screens/TestingScreen.tsx +++ b/apps/common-app/src/e2e_screens/TestingScreen.tsx @@ -1,15 +1,15 @@ -import { Pressable, StyleSheet, Text, View } from 'react-native'; -import { ScrollView } from 'react-native-gesture-handler'; +import { StyleSheet, Text, View } from 'react-native'; +import { ScrollView, Touchable } from 'react-native-gesture-handler'; interface TestingScreenProps { text: string; - setText: (text: string) => void; + buttonCallback: () => void; children: React.ReactNode; } export default function TestingScreen({ text, - setText, + buttonCallback, children, }: TestingScreenProps) { return ( @@ -23,14 +23,12 @@ export default function TestingScreen({ {children} - { - setText(''); - }}> - Reset - + + Extract callbacks + ); @@ -59,7 +57,7 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, - resetButton: { + extractButton: { width: 120, height: 40, borderRadius: 20, @@ -68,4 +66,7 @@ const styles = StyleSheet.create({ 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 index 615f93cd5b..930174404d 100644 --- a/apps/common-app/src/e2e_screens/gestures/Fling.tsx +++ b/apps/common-app/src/e2e_screens/gestures/Fling.tsx @@ -1,30 +1,37 @@ -import { useState } from 'react'; +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: () => { - setText((prev) => prev + '1'); + callbacks.current.add(CallbackIDs.onBegin); }, onActivate: () => { - setText((prev) => prev + '2'); + callbacks.current.add(CallbackIDs.onActivate); }, onDeactivate: () => { - setText((prev) => prev + '4'); + callbacks.current.add(CallbackIDs.onDeactivate); }, onFinalize: () => { - setText((prev) => prev + '5'); + callbacks.current.add(CallbackIDs.onFinalize); }, runOnJS: true, }); return ( - + { + setText(`{Fling: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> diff --git a/apps/common-app/src/e2e_screens/gestures/LongPress.tsx b/apps/common-app/src/e2e_screens/gestures/LongPress.tsx index 37e5f6afd4..f2833f394c 100644 --- a/apps/common-app/src/e2e_screens/gestures/LongPress.tsx +++ b/apps/common-app/src/e2e_screens/gestures/LongPress.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useRef, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { GestureDetector, @@ -6,28 +6,35 @@ import { } 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: () => { - setText((prev) => prev + '1'); + callbacks.current.add(CallbackIDs.onBegin); }, onActivate: () => { - setText((prev) => prev + '2'); + callbacks.current.add(CallbackIDs.onActivate); }, onDeactivate: () => { - setText((prev) => prev + '4'); + callbacks.current.add(CallbackIDs.onDeactivate); }, onFinalize: () => { - setText((prev) => prev + '5'); + callbacks.current.add(CallbackIDs.onFinalize); }, runOnJS: true, }); return ( - + { + setText(`{LongPress: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> diff --git a/apps/common-app/src/e2e_screens/gestures/Pan.tsx b/apps/common-app/src/e2e_screens/gestures/Pan.tsx index 5f67e4fa85..35acf0f943 100644 --- a/apps/common-app/src/e2e_screens/gestures/Pan.tsx +++ b/apps/common-app/src/e2e_screens/gestures/Pan.tsx @@ -1,37 +1,40 @@ -import { useState } from 'react'; +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: () => { - // Skip subsequent updates - if (text[text.length - 1] === '3') { - return; - } - setText((prev) => 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 ( - + { + setText(`{Pan: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> diff --git a/apps/common-app/src/e2e_screens/gestures/Pinch.tsx b/apps/common-app/src/e2e_screens/gestures/Pinch.tsx index c2285c24c9..fbef0b8f41 100644 --- a/apps/common-app/src/e2e_screens/gestures/Pinch.tsx +++ b/apps/common-app/src/e2e_screens/gestures/Pinch.tsx @@ -1,37 +1,40 @@ -import { useState } from 'react'; +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: () => { - setText((prev) => prev + '1'); + callbacks.current.add(CallbackIDs.onBegin); }, onActivate: () => { - setText((prev) => prev + '2'); + callbacks.current.add(CallbackIDs.onActivate); }, onUpdate: () => { - // Skip subsequent updates - if (text[text.length - 1] === '3') { - return; - } - setText((prev) => 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 ( - + { + setText(`{Pinch: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> diff --git a/apps/common-app/src/e2e_screens/gestures/Rotation.tsx b/apps/common-app/src/e2e_screens/gestures/Rotation.tsx index 7e00dc4d81..5421f7f33a 100644 --- a/apps/common-app/src/e2e_screens/gestures/Rotation.tsx +++ b/apps/common-app/src/e2e_screens/gestures/Rotation.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useRef, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { GestureDetector, @@ -6,35 +6,38 @@ import { } 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: () => { - setText((prev) => prev + '1'); + callbacks.current.add(CallbackIDs.onBegin); }, onActivate: () => { - setText((prev) => prev + '2'); + callbacks.current.add(CallbackIDs.onActivate); }, onUpdate: () => { - // Skip subsequent updates - if (text[text.length - 1] === '3') { - return; - } - setText((prev) => 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 ( - + { + setText(`{Rotation: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> diff --git a/apps/common-app/src/e2e_screens/gestures/Tap.tsx b/apps/common-app/src/e2e_screens/gestures/Tap.tsx index ed4d7dbc28..283a58fad2 100644 --- a/apps/common-app/src/e2e_screens/gestures/Tap.tsx +++ b/apps/common-app/src/e2e_screens/gestures/Tap.tsx @@ -1,30 +1,37 @@ -import { useState } from 'react'; +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: () => { - setText((prev) => prev + '1'); + callbacks.current.add(CallbackIDs.onBegin); }, onActivate: () => { - setText((prev) => prev + '2'); + callbacks.current.add(CallbackIDs.onActivate); }, onDeactivate: () => { - setText((prev) => prev + '4'); + callbacks.current.add(CallbackIDs.onDeactivate); }, onFinalize: () => { - setText((prev) => prev + '5'); + callbacks.current.add(CallbackIDs.onFinalize); }, runOnJS: true, }); return ( - + { + setText(`{Tap: ${Array.from(callbacks.current).join('')}}`); + callbacks.current.clear(); + }}> 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/screenNames.ts b/apps/common-app/src/e2e_screens/utils.ts similarity index 53% rename from apps/common-app/src/e2e_screens/screenNames.ts rename to apps/common-app/src/e2e_screens/utils.ts index 8c09d536a5..fa07686526 100644 --- a/apps/common-app/src/e2e_screens/screenNames.ts +++ b/apps/common-app/src/e2e_screens/utils.ts @@ -5,4 +5,13 @@ export const TestScreens = { Fling: '[E2E] Fling', Pinch: '[E2E] Pinch', Rotation: '[E2E] Rotation', + Competing: '[E2E] Competing', +}; + +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 46c9f79845..90faa64257 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -5,7 +5,8 @@ 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 { TestScreens } from '../e2e_screens/screenNames'; +import CompetingE2E from '../e2e_screens/relations/Competing'; +import { TestScreens } from '../e2e_screens/utils'; import EmptyExample from '../empty'; import CameraExample from './complicated/camera'; import ChatHeadsExample from './complicated/chat_heads'; @@ -150,6 +151,7 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ { name: TestScreens.Tap, component: TapE2E }, { name: TestScreens.LongPress, component: LongPressE2E }, { name: TestScreens.Fling, component: FlingE2E }, + { name: TestScreens.Competing, component: CompetingE2E }, ], }, ]; diff --git a/apps/expo-example/e2e/suites/fling.ts b/apps/expo-example/e2e/suites/fling.ts index f5b8d57eb8..dc4eded0ef 100644 --- a/apps/expo-example/e2e/suites/fling.ts +++ b/apps/expo-example/e2e/suites/fling.ts @@ -1,9 +1,8 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { navigateTo } from '../utils'; +import { CB, navigateTo, TestScreens } from '../utils'; export function flingTests() { describe('test fling gesture', () => { @@ -13,20 +12,20 @@ export function flingTests() { const gestureBox = element(by.id('fling-box')); const stateIndicator = element(by.id('state-indicator')); - const resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); + const extractButton = element(by.id('extract-button')); test('Should register fling gesture', async () => { await gestureBox.swipe('right', 'fast'); - await expect(stateIndicator).toHaveText('1245'); + 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 expect(stateIndicator).toHaveText('15'); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{Fling: ${CB.B}${CB.F}}`); }); }); } diff --git a/apps/expo-example/e2e/suites/long_press.ts b/apps/expo-example/e2e/suites/long_press.ts index acd1162f31..be87139a12 100644 --- a/apps/expo-example/e2e/suites/long_press.ts +++ b/apps/expo-example/e2e/suites/long_press.ts @@ -1,9 +1,8 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { navigateTo } from '../utils'; +import { CB, navigateTo, TestScreens } from '../utils'; export function longPressTests() { describe('test long press gesture', () => { @@ -13,20 +12,20 @@ export function longPressTests() { const gestureBox = element(by.id('long-press-box')); const stateIndicator = element(by.id('state-indicator')); - const resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); + const extractButton = element(by.id('extract-button')); test('Should register long press gesture', async () => { await gestureBox.longPress(1000); - await expect(stateIndicator).toHaveText('1245'); + 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 expect(stateIndicator).toHaveText('15'); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{LongPress: ${CB.B}${CB.F}}`); }); }); } diff --git a/apps/expo-example/e2e/suites/pan.ts b/apps/expo-example/e2e/suites/pan.ts index 966cb921b0..39d77ad1cc 100644 --- a/apps/expo-example/e2e/suites/pan.ts +++ b/apps/expo-example/e2e/suites/pan.ts @@ -1,9 +1,8 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { navigateTo } from '../utils'; +import { CB, navigateTo, TestScreens } from '../utils'; export function panTests() { describe('test pan gesture', () => { @@ -13,20 +12,20 @@ export function panTests() { 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(); - }); + const extractButton = element(by.id('extract-button')); test("Shouldn't register a pan gesture on tap", async () => { await gestureBox.tap(); - await expect(stateIndicator).toHaveText('15'); + 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 expect(stateIndicator).toHaveText('12345'); + 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/pinch.ts b/apps/expo-example/e2e/suites/pinch.ts index d705227306..077cdeb8e3 100644 --- a/apps/expo-example/e2e/suites/pinch.ts +++ b/apps/expo-example/e2e/suites/pinch.ts @@ -1,11 +1,10 @@ import { spawn } from 'node:child_process'; // eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { navigateTo } from '../utils'; +import { CB, navigateTo, TestScreens } from '../utils'; type PinchArgs = { centerX: string; @@ -51,11 +50,7 @@ export function pinchTests() { const gestureBox = element(by.id('pinch-box')); const stateIndicator = element(by.id('state-indicator')); - const resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); + const extractButton = element(by.id('extract-button')); test('Should register pinch gesture on pinch', async () => { const udid = device.id; @@ -67,12 +62,16 @@ export function pinchTests() { endDistance: '0.6', }); - await expect(stateIndicator).toHaveText('12345'); + 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 expect(stateIndicator).toHaveText(''); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{Pinch: }`); }); }); } diff --git a/apps/expo-example/e2e/suites/rotation.ts b/apps/expo-example/e2e/suites/rotation.ts index ceab87da3c..2051500bcb 100644 --- a/apps/expo-example/e2e/suites/rotation.ts +++ b/apps/expo-example/e2e/suites/rotation.ts @@ -1,11 +1,10 @@ import { spawn } from 'node:child_process'; // eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { navigateTo } from '../utils'; +import { CB, navigateTo, TestScreens } from '../utils'; type RotationArgs = { centerX: string; @@ -54,11 +53,7 @@ export function rotationTests() { const gestureBox = element(by.id('rotation-box')); const stateIndicator = element(by.id('state-indicator')); - const resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); + const extractButton = element(by.id('extract-button')); test('Should register rotation gesture on rotation', async () => { const udid = device.id; @@ -71,12 +66,16 @@ export function rotationTests() { radius: '0.05', }); - await expect(stateIndicator).toHaveText('12345'); + 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 expect(stateIndicator).toHaveText(''); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{Rotation: }`); }); }); } diff --git a/apps/expo-example/e2e/suites/tap.ts b/apps/expo-example/e2e/suites/tap.ts index a3c9c50954..6b99ad48b0 100644 --- a/apps/expo-example/e2e/suites/tap.ts +++ b/apps/expo-example/e2e/suites/tap.ts @@ -1,9 +1,8 @@ // eslint-disable-next-line import-x/no-extraneous-dependencies -import { beforeAll, beforeEach, describe } from '@jest/globals'; -import { TestScreens } from 'common-app/src/e2e_screens/screenNames'; +import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { navigateTo } from '../utils'; +import { CB, navigateTo, TestScreens } from '../utils'; export function tapTests() { describe('test tap gesture', () => { @@ -13,20 +12,20 @@ export function tapTests() { const gestureBox = element(by.id('tap-box')); const stateIndicator = element(by.id('state-indicator')); - const resetButton = element(by.id('reset')); - - beforeEach(async () => { - await resetButton.tap(); - }); + const extractButton = element(by.id('extract-button')); test('Should register tap gesture', async () => { await gestureBox.tap(); - await expect(stateIndicator).toHaveText('1245'); + 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 expect(stateIndicator).toHaveText('15'); + await extractButton.tap(); + await expect(stateIndicator).toHaveText(`{Tap: ${CB.B}${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'; From fc84a53ab8050bb05f8bb70916debc23d4197350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 May 2026 12:07:25 +0200 Subject: [PATCH 19/22] Remove argent from CI --- .github/workflows/e2e.yml | 4 +-- apps/expo-example/e2e/__tests__/all.test.ts | 14 ++++---- .../e2e/__tests__/all_wo_argent.test.ts | 11 +++++++ .../e2e/__tests__/gestures/fling.test.ts | 2 +- .../e2e/__tests__/gestures/long_press.test.ts | 2 +- .../e2e/__tests__/gestures/pan.test.ts | 2 +- .../e2e/__tests__/gestures/pinch.test.ts | 2 +- .../e2e/__tests__/gestures/rotation.test.ts | 2 +- .../e2e/__tests__/gestures/tap.test.ts | 2 +- .../e2e/__tests__/relations/competing.test.ts | 3 ++ .../e2e/suites/{ => gestures}/fling.ts | 2 +- .../e2e/suites/{ => gestures}/long_press.ts | 2 +- .../e2e/suites/{ => gestures}/pan.ts | 2 +- .../e2e/suites/{ => gestures}/pinch.ts | 2 +- .../e2e/suites/{ => gestures}/rotation.ts | 2 +- .../e2e/suites/{ => gestures}/tap.ts | 2 +- .../e2e/suites/relations/competing.ts | 33 +++++++++++++++++++ apps/expo-example/package.json | 4 ++- 18 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 apps/expo-example/e2e/__tests__/all_wo_argent.test.ts create mode 100644 apps/expo-example/e2e/__tests__/relations/competing.test.ts rename apps/expo-example/e2e/suites/{ => gestures}/fling.ts (94%) rename apps/expo-example/e2e/suites/{ => gestures}/long_press.ts (94%) rename apps/expo-example/e2e/suites/{ => gestures}/pan.ts (94%) rename apps/expo-example/e2e/suites/{ => gestures}/pinch.ts (96%) rename apps/expo-example/e2e/suites/{ => gestures}/rotation.ts (97%) rename apps/expo-example/e2e/suites/{ => gestures}/tap.ts (94%) create mode 100644 apps/expo-example/e2e/suites/relations/competing.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0c3c555210..218d0f87d7 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -50,7 +50,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' @@ -101,4 +101,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/expo-example/e2e/__tests__/all.test.ts b/apps/expo-example/e2e/__tests__/all.test.ts index f1c581a5b6..730303eca1 100644 --- a/apps/expo-example/e2e/__tests__/all.test.ts +++ b/apps/expo-example/e2e/__tests__/all.test.ts @@ -1,9 +1,10 @@ -import { flingTests } from '../suites/fling'; -import { longPressTests } from '../suites/long_press'; -import { panTests } from '../suites/pan'; -import { pinchTests } from '../suites/pinch'; -import { rotationTests } from '../suites/rotation'; -import { tapTests } from '../suites/tap'; +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'; tapTests(); pinchTests(); @@ -11,3 +12,4 @@ flingTests(); longPressTests(); rotationTests(); panTests(); +competingTests(); 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..c2b8e6d198 --- /dev/null +++ b/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts @@ -0,0 +1,11 @@ +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'; + +tapTests(); +flingTests(); +longPressTests(); +panTests(); +competingTests(); diff --git a/apps/expo-example/e2e/__tests__/gestures/fling.test.ts b/apps/expo-example/e2e/__tests__/gestures/fling.test.ts index f2e2820121..66cea816a2 100644 --- a/apps/expo-example/e2e/__tests__/gestures/fling.test.ts +++ b/apps/expo-example/e2e/__tests__/gestures/fling.test.ts @@ -1,3 +1,3 @@ -import { flingTests } from '../../suites/fling'; +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 index 53b3b6be64..d3e5455e7e 100644 --- a/apps/expo-example/e2e/__tests__/gestures/long_press.test.ts +++ b/apps/expo-example/e2e/__tests__/gestures/long_press.test.ts @@ -1,3 +1,3 @@ -import { longPressTests } from '../../suites/long_press'; +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 index ca1f68d2ed..0451b22c71 100644 --- a/apps/expo-example/e2e/__tests__/gestures/pan.test.ts +++ b/apps/expo-example/e2e/__tests__/gestures/pan.test.ts @@ -1,3 +1,3 @@ -import { panTests } from '../../suites/pan'; +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 index 1fc2ef3ae6..9876ea71ce 100644 --- a/apps/expo-example/e2e/__tests__/gestures/pinch.test.ts +++ b/apps/expo-example/e2e/__tests__/gestures/pinch.test.ts @@ -1,3 +1,3 @@ -import { pinchTests } from '../../suites/pinch'; +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 index 2f87ca0c2f..3949a22b66 100644 --- a/apps/expo-example/e2e/__tests__/gestures/rotation.test.ts +++ b/apps/expo-example/e2e/__tests__/gestures/rotation.test.ts @@ -1,3 +1,3 @@ -import { rotationTests } from '../../suites/rotation'; +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 index 457dba983f..9a70b42174 100644 --- a/apps/expo-example/e2e/__tests__/gestures/tap.test.ts +++ b/apps/expo-example/e2e/__tests__/gestures/tap.test.ts @@ -1,3 +1,3 @@ -import { tapTests } from '../../suites/tap'; +import { tapTests } from '../../suites/gestures/tap'; tapTests(); 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/suites/fling.ts b/apps/expo-example/e2e/suites/gestures/fling.ts similarity index 94% rename from apps/expo-example/e2e/suites/fling.ts rename to apps/expo-example/e2e/suites/gestures/fling.ts index dc4eded0ef..31544666e0 100644 --- a/apps/expo-example/e2e/suites/fling.ts +++ b/apps/expo-example/e2e/suites/gestures/fling.ts @@ -2,7 +2,7 @@ import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { CB, navigateTo, TestScreens } from '../utils'; +import { CB, navigateTo, TestScreens } from '../../utils'; export function flingTests() { describe('test fling gesture', () => { diff --git a/apps/expo-example/e2e/suites/long_press.ts b/apps/expo-example/e2e/suites/gestures/long_press.ts similarity index 94% rename from apps/expo-example/e2e/suites/long_press.ts rename to apps/expo-example/e2e/suites/gestures/long_press.ts index be87139a12..a05d7e1ab4 100644 --- a/apps/expo-example/e2e/suites/long_press.ts +++ b/apps/expo-example/e2e/suites/gestures/long_press.ts @@ -2,7 +2,7 @@ import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { CB, navigateTo, TestScreens } from '../utils'; +import { CB, navigateTo, TestScreens } from '../../utils'; export function longPressTests() { describe('test long press gesture', () => { diff --git a/apps/expo-example/e2e/suites/pan.ts b/apps/expo-example/e2e/suites/gestures/pan.ts similarity index 94% rename from apps/expo-example/e2e/suites/pan.ts rename to apps/expo-example/e2e/suites/gestures/pan.ts index 39d77ad1cc..851c839f5d 100644 --- a/apps/expo-example/e2e/suites/pan.ts +++ b/apps/expo-example/e2e/suites/gestures/pan.ts @@ -2,7 +2,7 @@ import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { CB, navigateTo, TestScreens } from '../utils'; +import { CB, navigateTo, TestScreens } from '../../utils'; export function panTests() { describe('test pan gesture', () => { diff --git a/apps/expo-example/e2e/suites/pinch.ts b/apps/expo-example/e2e/suites/gestures/pinch.ts similarity index 96% rename from apps/expo-example/e2e/suites/pinch.ts rename to apps/expo-example/e2e/suites/gestures/pinch.ts index 077cdeb8e3..f9096584b4 100644 --- a/apps/expo-example/e2e/suites/pinch.ts +++ b/apps/expo-example/e2e/suites/gestures/pinch.ts @@ -4,7 +4,7 @@ import { spawn } from 'node:child_process'; import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { CB, navigateTo, TestScreens } from '../utils'; +import { CB, navigateTo, TestScreens } from '../../utils'; type PinchArgs = { centerX: string; diff --git a/apps/expo-example/e2e/suites/rotation.ts b/apps/expo-example/e2e/suites/gestures/rotation.ts similarity index 97% rename from apps/expo-example/e2e/suites/rotation.ts rename to apps/expo-example/e2e/suites/gestures/rotation.ts index 2051500bcb..8827e406bb 100644 --- a/apps/expo-example/e2e/suites/rotation.ts +++ b/apps/expo-example/e2e/suites/gestures/rotation.ts @@ -4,7 +4,7 @@ import { spawn } from 'node:child_process'; import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { CB, navigateTo, TestScreens } from '../utils'; +import { CB, navigateTo, TestScreens } from '../../utils'; type RotationArgs = { centerX: string; diff --git a/apps/expo-example/e2e/suites/tap.ts b/apps/expo-example/e2e/suites/gestures/tap.ts similarity index 94% rename from apps/expo-example/e2e/suites/tap.ts rename to apps/expo-example/e2e/suites/gestures/tap.ts index 6b99ad48b0..23b75d7001 100644 --- a/apps/expo-example/e2e/suites/tap.ts +++ b/apps/expo-example/e2e/suites/gestures/tap.ts @@ -2,7 +2,7 @@ import { beforeAll, describe } from '@jest/globals'; import { by, element, expect } from 'detox'; -import { CB, navigateTo, TestScreens } from '../utils'; +import { CB, navigateTo, TestScreens } from '../../utils'; export function tapTests() { describe('test tap gesture', () => { 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/package.json b/apps/expo-example/package.json index 18866c50a8..f08db5d08c 100644 --- a/apps/expo-example/package.json +++ b/apps/expo-example/package.json @@ -14,7 +14,9 @@ "build-detox": "yarn build-detox-android && yarn build-detox-ios", "test": "jest", "e2e:ios": "detox test -c ios.sim.release e2e/__tests__/all.test.ts", - "e2e:android": "detox test -c android.emu.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", From 7028e1d5cc28743e7e1c7bfec16f42898be792e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 May 2026 12:09:26 +0200 Subject: [PATCH 20/22] Do not install argent in CI --- .github/workflows/e2e.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 218d0f87d7..02020acb45 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -38,9 +38,6 @@ jobs: - name: Install dependencies run: yarn --immutable - - name: Install Argent - run: npm install -g @swmansion/argent - - name: Setup Detox run: brew tap wix/brew && brew install applesimutils && npm install -g detox-cli @@ -81,9 +78,6 @@ jobs: - name: Install dependencies run: yarn --immutable - - name: Install Argent - run: npm install -g @swmansion/argent - - name: Setup Detox run: npm install -g detox-cli From e5c26e2cd0e7ffa32a975c93999557866b9fc861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 May 2026 12:30:01 +0200 Subject: [PATCH 21/22] Add exclusive test --- .../src/e2e_screens/relations/Exclusive.tsx | 83 +++++++++++++++++++ apps/common-app/src/e2e_screens/utils.ts | 1 + apps/common-app/src/new_api/index.tsx | 2 + apps/expo-example/e2e/__tests__/all.test.ts | 2 + .../e2e/__tests__/all_wo_argent.test.ts | 2 + .../e2e/__tests__/relations/exclusive.test.ts | 3 + .../e2e/suites/relations/exclusive.ts | 33 ++++++++ 7 files changed, 126 insertions(+) create mode 100644 apps/common-app/src/e2e_screens/relations/Exclusive.tsx create mode 100644 apps/expo-example/e2e/__tests__/relations/exclusive.test.ts create mode 100644 apps/expo-example/e2e/suites/relations/exclusive.ts 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/utils.ts b/apps/common-app/src/e2e_screens/utils.ts index fa07686526..6ec6f1ab04 100644 --- a/apps/common-app/src/e2e_screens/utils.ts +++ b/apps/common-app/src/e2e_screens/utils.ts @@ -6,6 +6,7 @@ export const TestScreens = { Pinch: '[E2E] Pinch', Rotation: '[E2E] Rotation', Competing: '[E2E] Competing', + Exclusive: '[E2E] Exclusive', }; export const CallbackIDs = { diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index 90faa64257..a644f1d5a0 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -6,6 +6,7 @@ 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 { TestScreens } from '../e2e_screens/utils'; import EmptyExample from '../empty'; import CameraExample from './complicated/camera'; @@ -152,6 +153,7 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ { name: TestScreens.LongPress, component: LongPressE2E }, { name: TestScreens.Fling, component: FlingE2E }, { name: TestScreens.Competing, component: CompetingE2E }, + { name: TestScreens.Exclusive, component: ExclusiveE2E }, ], }, ]; diff --git a/apps/expo-example/e2e/__tests__/all.test.ts b/apps/expo-example/e2e/__tests__/all.test.ts index 730303eca1..8ce4057b14 100644 --- a/apps/expo-example/e2e/__tests__/all.test.ts +++ b/apps/expo-example/e2e/__tests__/all.test.ts @@ -5,6 +5,7 @@ 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'; tapTests(); pinchTests(); @@ -13,3 +14,4 @@ longPressTests(); rotationTests(); panTests(); competingTests(); +exclusiveTests(); diff --git a/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts b/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts index c2b8e6d198..df329370b2 100644 --- a/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts +++ b/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts @@ -3,9 +3,11 @@ 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'; tapTests(); flingTests(); longPressTests(); panTests(); competingTests(); +exclusiveTests(); 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/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}}` + ); + }); + }); +} From ee42cccde35598b5ed6df2ee9185c518bfd8fc4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Tue, 26 May 2026 16:14:15 +0200 Subject: [PATCH 22/22] Add simultaneous --- .../e2e_screens/relations/Simultaneous.tsx | 84 +++++++++++++++++++ apps/common-app/src/e2e_screens/utils.ts | 1 + apps/common-app/src/new_api/index.tsx | 2 + apps/expo-example/e2e/__tests__/all.test.ts | 2 + .../e2e/__tests__/all_wo_argent.test.ts | 2 + .../__tests__/relations/simultaneous.test.ts | 3 + .../e2e/suites/relations/simultaneous.ts | 25 ++++++ 7 files changed, 119 insertions(+) create mode 100644 apps/common-app/src/e2e_screens/relations/Simultaneous.tsx create mode 100644 apps/expo-example/e2e/__tests__/relations/simultaneous.test.ts create mode 100644 apps/expo-example/e2e/suites/relations/simultaneous.ts 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 index 6ec6f1ab04..14bc515019 100644 --- a/apps/common-app/src/e2e_screens/utils.ts +++ b/apps/common-app/src/e2e_screens/utils.ts @@ -7,6 +7,7 @@ export const TestScreens = { Rotation: '[E2E] Rotation', Competing: '[E2E] Competing', Exclusive: '[E2E] Exclusive', + Simultaneous: '[E2E] Simultaneous', }; export const CallbackIDs = { diff --git a/apps/common-app/src/new_api/index.tsx b/apps/common-app/src/new_api/index.tsx index a644f1d5a0..6e793e77e7 100644 --- a/apps/common-app/src/new_api/index.tsx +++ b/apps/common-app/src/new_api/index.tsx @@ -7,6 +7,7 @@ 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'; @@ -154,6 +155,7 @@ export const NEW_EXAMPLES: ExamplesSection[] = [ { 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 index 8ce4057b14..99d4a639a9 100644 --- a/apps/expo-example/e2e/__tests__/all.test.ts +++ b/apps/expo-example/e2e/__tests__/all.test.ts @@ -6,6 +6,7 @@ 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(); @@ -15,3 +16,4 @@ 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 index df329370b2..b90e788034 100644 --- a/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts +++ b/apps/expo-example/e2e/__tests__/all_wo_argent.test.ts @@ -4,6 +4,7 @@ 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(); @@ -11,3 +12,4 @@ longPressTests(); panTests(); competingTests(); exclusiveTests(); +simultaneousTests(); 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/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}}` + ); + }); + }); +}