Skip to content

Commit 958acd2

Browse files
committed
Tap e2e test
1 parent fb1089b commit 958acd2

6 files changed

Lines changed: 248 additions & 10 deletions

File tree

.argent/flows/simple-tap-test.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
steps:
2+
- echo: Launching Expo Example
3+
- launch: com.example.ExpoExample
4+
- await: { visible: { text: "Empty Example" }, timeout: 15000 }
5+
- echo: "On the Home list, search for tap example"
6+
- type: { text: "tap", into: { id: "search-examples" } }
7+
- echo: "Tap the example to open it"
8+
- tap: "Basic Tap"
9+
- echo: "Clear console"
10+
- await: { visible: "open-console-button" }
11+
- tap: "open-console-button"
12+
- await: { visible: "clear-console-button" }
13+
- tap: "clear-console-button"
14+
- tap: "close-console-button"
15+
- wait: 1000 # TODO: investigate why this is needed (closing modal intercepts tap on the box)
16+
- echo: "Tap the box and check if tap count is updated"
17+
- await: { visible: "Tap count: 0" }
18+
- assert: { visible: "tap-box" }
19+
- tap: "tap-box"
20+
- assert: { visible: "Tap count: 1" }
21+
- echo: "Check if console logs are printed in order"
22+
- tap: "open-console-button"
23+
- await: { visible: "close-console-button" }
24+
- assert: { visible: "1. onBegin" }
25+
- assert: { visible: "2. onActivate" }
26+
- assert: { visible: "3. onDeactivate" }
27+
- assert: { visible: "4. onFinalize" }
28+
- tap: "close-console-button"
29+
- wait: 1000 # TODO: investigate why this is needed (closing modal intercepts tap on the box)
30+
- await: { visible: "Tap count: 1" }
31+
- echo: "Tap the box again and check if tap count is updated"
32+
- tap: "tap-box"
33+
- assert: { visible: "Tap count: 2" }

.github/workflows/android-e2e.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Test Android e2e
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- .argent/flows/*
7+
- .github/workflows/android-e2e.yml
8+
- packages/react-native-gesture-handler/android/**
9+
- apps/expo-example/android/**
10+
- apps/expo-example/package.json
11+
- rnrepo.config.json
12+
- yarn.lock
13+
push:
14+
branches:
15+
- main
16+
workflow_dispatch:
17+
18+
jobs:
19+
build:
20+
if: github.repository == 'software-mansion/react-native-gesture-handler'
21+
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 60
24+
concurrency:
25+
group: android-e2e-${{ github.ref }}
26+
cancel-in-progress: true
27+
28+
steps:
29+
- name: checkout
30+
uses: actions/checkout@v4
31+
32+
- name: Use Java 17
33+
uses: actions/setup-java@v4
34+
with:
35+
distribution: oracle
36+
java-version: 17
37+
38+
- name: Use Node.js 24
39+
uses: actions/setup-node@v6
40+
with:
41+
node-version: 24
42+
cache: yarn
43+
44+
- name: Install node dependencies
45+
working-directory: apps/expo-example
46+
run: yarn install --immutable
47+
48+
- name: Build app in Release mode
49+
working-directory: apps/expo-example/android
50+
run: ./gradlew :app:assembleRelease --console=plain -PreactNativeArchitectures=x86_64
51+
52+
- name: Install Agent
53+
run: npx @swmansion/argent init --yes
54+
55+
- name: Enable KVM
56+
run: |
57+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
58+
sudo udevadm control --reload-rules
59+
sudo udevadm trigger --name-match=kvm
60+
61+
- name: Set up emulator and run tap E2E test
62+
uses: reactivecircus/android-emulator-runner@v2
63+
with:
64+
api-level: 34
65+
target: google_apis
66+
arch: x86_64
67+
force-avd-creation: false
68+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
69+
disable-animations: true
70+
script: |
71+
APK=$(find apps/expo-example/android/app/build/outputs/apk/release -name "*.apk" | head -1)
72+
if [ -z "$APK" ]; then
73+
echo "Could not find built .apk" >&2
74+
exit 1
75+
fi
76+
echo "Installing $APK"
77+
adb install -r "$APK"
78+
SERIAL=$(adb devices | awk 'NR>1 && $2=="device"{print $1; exit}')
79+
echo "Using emulator $SERIAL"
80+
argent flow run simple-tap-test --device "$SERIAL"

.github/workflows/ios-e2e.yml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Test iOS e2e
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- .argent/flows/*
7+
- .github/workflows/ios-e2e.yml
8+
- packages/react-native-gesture-handler/RNGestureHandler.podspec
9+
- packages/react-native-gesture-handler/apple/**
10+
- apps/expo-example/ios/**
11+
- apps/expo-example/package.json
12+
- rnrepo.config.json
13+
- yarn.lock
14+
push:
15+
branches:
16+
- main
17+
workflow_dispatch:
18+
19+
jobs:
20+
build:
21+
if: github.repository == 'software-mansion/react-native-gesture-handler'
22+
23+
runs-on: macos-26
24+
timeout-minutes: 60
25+
concurrency:
26+
group: ios-e2e-${{ github.ref }}
27+
cancel-in-progress: true
28+
29+
steps:
30+
- name: checkout
31+
uses: actions/checkout@v4
32+
33+
- name: Use latest stable Xcode
34+
uses: maxim-lobanov/setup-xcode@v1
35+
with:
36+
xcode-version: '26.4.1'
37+
38+
- name: Use Node.js 24
39+
uses: actions/setup-node@v6
40+
with:
41+
node-version: 24
42+
cache: yarn
43+
44+
- name: Install node dependencies
45+
working-directory: apps/expo-example
46+
run: yarn install --immutable
47+
48+
- name: Build app in Release mode
49+
working-directory: apps/expo-example/ios
50+
run: |
51+
xcodebuild \
52+
-workspace ExpoExample.xcworkspace \
53+
-scheme ExpoExample \
54+
-configuration Release \
55+
-sdk iphonesimulator \
56+
-destination 'generic/platform=iOS Simulator' \
57+
-derivedDataPath build \
58+
CODE_SIGNING_ALLOWED=NO \
59+
build
60+
61+
- name: Set up iPhone 17 Pro simulator
62+
run: |
63+
UDID=$(xcrun simctl list devices available | grep -E "iPhone 17 Pro \(" | head -1 | grep -oE "[0-9A-Fa-f-]{36}" || true)
64+
if [ -z "$UDID" ]; then
65+
echo "iPhone 17 Pro not found, creating one"
66+
RUNTIME=$(xcrun simctl list runtimes ios -j | jq -r '.runtimes | map(select(.isAvailable)) | last | .identifier')
67+
UDID=$(xcrun simctl create "iPhone 17 Pro" "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro" "$RUNTIME")
68+
fi
69+
echo "Using simulator $UDID"
70+
xcrun simctl boot "$UDID" || true
71+
xcrun simctl bootstatus "$UDID"
72+
echo "SIMULATOR_UDID=$UDID" >> "$GITHUB_ENV"
73+
74+
- name: Install app on simulator
75+
working-directory: apps/expo-example/ios
76+
run: |
77+
APP_PATH=$(find build/Build/Products/Release-iphonesimulator -maxdepth 1 -name "*.app" -type d | head -1)
78+
if [ -z "$APP_PATH" ]; then
79+
echo "Could not find built .app" >&2
80+
exit 1
81+
fi
82+
echo "Installing $APP_PATH on $SIMULATOR_UDID"
83+
xcrun simctl install "$SIMULATOR_UDID" "$APP_PATH"
84+
85+
- name: Install Agent
86+
run: npx @swmansion/argent init --yes
87+
88+
- name: Run tap E2E test
89+
run: argent flow run simple-tap-test --device $SIMULATOR_UDID

apps/common-app/src/console/ConsoleModal.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ function ConsoleModalContent({ entries, visible, onClose }: ConsoleModalProps) {
236236
</View>
237237
<View style={styles.modalActions}>
238238
<Touchable
239+
testID="clear-console-button"
239240
accessibilityRole="button"
240241
disabled={entries.length === 0}
241242
activeOpacity={0.5}
@@ -250,6 +251,7 @@ function ConsoleModalContent({ entries, visible, onClose }: ConsoleModalProps) {
250251
</Text>
251252
</Touchable>
252253
<Touchable
254+
testID="close-console-button"
253255
accessibilityRole="button"
254256
accessibilityLabel="Close console"
255257
activeOpacity={0.5}

apps/common-app/src/new_api/index.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ export const NEW_EXAMPLES: ExamplesSection[] = [
5454
{
5555
sectionTitle: 'Simple Gestures',
5656
data: [
57-
{ name: 'Fling', component: FlingExample },
58-
{ name: 'Tap', component: TapExample },
59-
{ name: 'LongPress', component: LongPressExample },
60-
{ name: 'Hover', component: HoverExample },
61-
{ name: 'Pinch', component: PinchExample },
62-
{ name: 'Rotation', component: RotationExample },
63-
{ name: 'Pan', component: PanExample },
57+
{ name: 'Basic Fling', component: FlingExample },
58+
{ name: 'Basic Tap', component: TapExample },
59+
{ name: 'Basic LongPress', component: LongPressExample },
60+
{ name: 'Basic Hover', component: HoverExample },
61+
{ name: 'Basic Pinch', component: PinchExample },
62+
{ name: 'Basic Rotation', component: RotationExample },
63+
{ name: 'Basic Pan', component: PanExample },
6464
],
6565
},
6666
{

apps/common-app/src/new_api/simple/tap/index.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,38 @@
1-
import React from 'react';
2-
import { View } from 'react-native';
1+
import React, { useRef, useState } from 'react';
2+
import { Text, View } from 'react-native';
33
import { GestureDetector, useTapGesture } from 'react-native-gesture-handler';
44
import Animated, {
55
interpolateColor,
66
useAnimatedStyle,
77
useSharedValue,
88
withTiming,
99
} from 'react-native-reanimated';
10+
import { scheduleOnRN } from 'react-native-worklets';
1011

1112
import { COLORS, commonStyles } from '../../../common';
1213

14+
function useIndexedLogger() {
15+
const messageCounter = useRef(0);
16+
17+
const logMessage = (message: string) => {
18+
messageCounter.current += 1;
19+
const indexedMessage = `${messageCounter.current}. ${message}`;
20+
console.log(indexedMessage);
21+
};
22+
23+
const logMessageWorklet = (message: string) => {
24+
'worklet';
25+
// Schedule log on the JS thread so the console interceptor can pick it up
26+
scheduleOnRN(logMessage, message);
27+
};
28+
29+
return logMessageWorklet;
30+
}
31+
1332
export default function TapExample() {
33+
const [count, setCount] = useState(0);
1434
const colorProgress = useSharedValue(0);
35+
const log = useIndexedLogger();
1536

1637
const animatedStyle = useAnimatedStyle(() => {
1738
return {
@@ -25,11 +46,20 @@ export default function TapExample() {
2546

2647
const tapGesture = useTapGesture({
2748
onBegin: () => {
49+
log('onBegin');
2850
colorProgress.value = withTiming(1, {
2951
duration: 100,
3052
});
3153
},
54+
onActivate: () => {
55+
log('onActivate');
56+
scheduleOnRN(setCount, count + 1);
57+
},
58+
onDeactivate: () => {
59+
log('onDeactivate');
60+
},
3261
onFinalize: () => {
62+
log('onFinalize');
3363
colorProgress.value = withTiming(0, {
3464
duration: 100,
3565
});
@@ -38,8 +68,12 @@ export default function TapExample() {
3868

3969
return (
4070
<View style={commonStyles.centerView}>
71+
<Text style={commonStyles.header}>Tap count: {count}</Text>
4172
<GestureDetector gesture={tapGesture}>
42-
<Animated.View style={[commonStyles.box, animatedStyle]} />
73+
<Animated.View
74+
testID="tap-box"
75+
style={[commonStyles.box, animatedStyle]}
76+
/>
4377
</GestureDetector>
4478
</View>
4579
);

0 commit comments

Comments
 (0)