Skip to content

Commit 4606f20

Browse files
feat: add harness tests for turbo module
1 parent 9f36982 commit 4606f20

22 files changed

Lines changed: 2282 additions & 120 deletions

.github/CI_DOCUMENTATION.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# GitHub Actions CI/CD Documentation
2+
3+
This document describes the Continuous Integration setup for the mendix-native project.
4+
5+
## Overview
6+
7+
The CI pipeline runs on every pull request and includes:
8+
9+
1. **Linting** - Code quality checks
10+
2. **Building** - iOS and Android example app builds
11+
3. **Testing** - Automated harness tests on both platforms
12+
13+
## Tooling Versions
14+
15+
All workflows use standardized tooling versions to ensure consistency:
16+
17+
### Core Tools
18+
19+
| Tool | Version | Defined In |
20+
| ---------------- | -------- | ----------------------------- |
21+
| **Node.js** | `24` | `.nvmrc` |
22+
| **Yarn** | `4.12.0` | `package.json#packageManager` |
23+
| **React Native** | `0.78.2` | `package.json` |
24+
| **React** | `19.0.0` | `package.json` |
25+
| **TypeScript** | `5.9.2` | `package.json` |
26+
27+
### Android Tooling
28+
29+
| Tool | Version | Defined In |
30+
| ----------------------- | ------------------------ | ------------------------------ |
31+
| **Java** | `17` (Zulu distribution) | All Android workflows |
32+
| **Gradle** | `8.12` | `gradle-wrapper.properties` |
33+
| **Android Build Tools** | `35.0.0` | `example/android/build.gradle` |
34+
| **NDK** | `27.3.13750724` | `example/android/build.gradle` |
35+
| **Kotlin** | `2.0.21` | `example/android/build.gradle` |
36+
| **compileSdkVersion** | `35` | `example/android/build.gradle` |
37+
| **targetSdkVersion** | `35` | `example/android/build.gradle` |
38+
| **minSdkVersion** | `24` | `example/android/build.gradle` |
39+
40+
### iOS Tooling
41+
42+
| Tool | Version | Defined In |
43+
| ------------- | ----------------------------------- | -------------------------------- |
44+
| **Xcode** | `26.1` | All iOS workflows |
45+
| **iOS SDK** | `26.1` | Xcode 26.1 includes iOS 26.1 SDK |
46+
| **Ruby** | `3.2` | `ios.yml` |
47+
| **CocoaPods** | `>= 1.13` (excludes 1.15.0, 1.15.1) | `example/Gemfile` |
48+
49+
> **Note for Local Development:** GitHub Actions `macos-latest` runners use Xcode 26.1 as the default.
50+
> Your local machine may have a different Xcode version. The workflows are configured to match GitHub Actions'
51+
> environment. Local development can use any compatible Xcode version, but ensure simulator devices specified
52+
> in `rn-harness.config.mjs` are available on your system.
53+
54+
### Test Configuration
55+
56+
| Tool | Version | Defined In |
57+
| ------------------------ | --------------------- | ----------------------- |
58+
| **React Native Harness** | `1.0.0-alpha.21` | `package.json` |
59+
| **Android Emulator** | Pixel_API_34 (API 34) | `rn-harness.config.mjs` |
60+
| **iOS Simulator** | iPhone 17 (iOS 26.0+) | `rn-harness.config.mjs` |
61+
62+
### GitHub Actions
63+
64+
All actions are pinned to specific commit SHAs for security and reproducibility:
65+
66+
| Action | Version | SHA |
67+
| ---------------------------------------- | -------- | ------------------------------------------ |
68+
| `actions/checkout` | v4.2.2 | `93cb6efe18208431cddfb8368fd83d5badbf9bfd` |
69+
| `actions/setup-node` | v4.2.2 | `49933ea5288caeca8642d1e84afbd3f7d6820020` |
70+
| `actions/setup-java` | v4.7.1 | `f2beeb24e141e01a676f977032f5a29d81c9e27e` |
71+
| `actions/cache` | v4.2.0 | `0057852bfaa89a56745cba8c7296529d2fc39830` |
72+
| `ruby/setup-ruby` | v1.204.0 | `d697be2f83c6234b20877c3b5eac7a7f342f0d0c` |
73+
| `android-actions/setup-android` | v3.2.1 | `9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407` |
74+
| `reactivecircus/android-emulator-runner` | v2.33.0 | `b530d96654c385303d652368551fb075bc2f0b6b` |
75+
| `futureware-tech/simulator-action` | v3.1.1 | `dab10d813144ef59b48d401cd95da151222ef8cd` |
76+
77+
### Version Consistency Rules
78+
79+
**IMPORTANT**: When updating versions, maintain consistency:
80+
81+
1. **iOS Simulator Device**: Must match between:
82+
83+
- `rn-harness.config.mjs` (test configuration)
84+
- `ios.yml` (xcodebuild destination)
85+
- `example/package.json` (ios:simulator:build script)
86+
- Must be available in the specified Xcode version
87+
88+
2. **Xcode Version**: Must match in:
89+
90+
- `ios.yml` (xcode-select command and DEVELOPER_DIR env var)
91+
- iOS simulator version in harness config must be compatible
92+
93+
3. **Android Emulator Device**: Must match between:
94+
95+
- `rn-harness.config.mjs` (test configuration)
96+
- `android.yml` (AVD_NAME environment variable)
97+
98+
4. **NDK Version**: Must match between:
99+
100+
- `example/android/build.gradle` (ndkVersion)
101+
- `android.yml` (sdkmanager install command and cache path)
102+
103+
5. **Java Version**: Must be specified in:
104+
105+
- `android.yml` (Setup Java step)
106+
107+
6. **Ruby Version**: Must satisfy:
108+
109+
- `ios.yml` specification (3.2)
110+
- `example/Gemfile` requirement (>= 2.6.10)
111+
- `ios.yml` working-directory must point to `example` (where Gemfile lives)
112+
113+
7. **React Native Harness**: Must match between:
114+
- `example/package.json`
115+
- All harness platform packages (`@react-native-harness/*`)
116+
117+
## Workflows
118+
119+
#### 1. Lint (`.github/workflows/lint.yml`)
120+
121+
- Runs ESLint and TypeScript checks
122+
123+
#### 2. Android Build & Test (`.github/workflows/android.yml`)
124+
125+
**Single integrated job that:**
126+
127+
- Builds the Android example app (debug APK)
128+
- Sets up Android emulator
129+
- Runs harness tests
130+
- Runs on: `ubuntu-latest`
131+
- Uses: Java 17, Android SDK, NDK 27.3.13750724
132+
133+
#### 3. iOS Build & Test (`.github/workflows/ios.yml`)
134+
135+
**Single integrated job that:**
136+
137+
- Builds the iOS example app for simulator
138+
- Sets up iOS simulator
139+
- Runs harness tests
140+
- Runs on: `macos-latest`
141+
- Uses: Xcode 26.1, Ruby 3.2, CocoaPods
142+
143+
## Test Configuration
144+
145+
The harness tests are configured in `example/rn-harness.config.mjs`:
146+
147+
```javascript
148+
runners: [
149+
androidPlatform({
150+
name: 'android',
151+
device: androidEmulator('Pixel_API_34'),
152+
bundleId: 'mendixnative.example',
153+
}),
154+
applePlatform({
155+
name: 'ios',
156+
device: appleSimulator('iPhone 17', '26.0'),
157+
bundleId: 'mendixnative.example',
158+
}),
159+
];
160+
```
161+
162+
The workflows handle device setup automatically using `reactivecircus/android-emulator-runner` and `futureware-tech/simulator-action`, reading device configuration from the harness config.
163+
164+
## Running Tests Locally
165+
166+
### iOS
167+
168+
Make sure `iPhone 17 (iOS 26.0)` simulator is up and running
169+
170+
```bash
171+
corepack enable
172+
yarn install
173+
yarn prepare
174+
cd example
175+
yarn pod
176+
yarn harness:ios:with:build
177+
```
178+
179+
### Android
180+
181+
Make sure emulator with name `Pixel_API_34` is up and running
182+
183+
```bash
184+
corepack enable
185+
yarn install
186+
yarn prepare
187+
cd example
188+
yarn harness:android:with:build
189+
```
190+
191+
## Troubleshooting
192+
193+
### Device/Emulator Issues
194+
195+
**Android emulator fails to boot or tests time out:**
196+
197+
- Check AVD cache - clear `avd-*` entries if corrupted
198+
- Verify KVM is enabled (workflows handle this automatically)
199+
- Check emulator logs in workflow output
200+
- Ensure AVD_NAME matches harness config (`Pixel_API_34`)
201+
202+
**iOS simulator not found or fails to boot:**
203+
204+
- Verify simulator model exists in Xcode version (`iPhone 17`)
205+
- Check iOS version compatibility (`26.0`)
206+
- Review simulator-action logs for setup errors
207+
- Ensure device name matches harness config exactly
208+
209+
## References
210+
211+
- [React Native Harness Documentation](https://www.react-native-harness.dev/)
212+
- [React Native Harness CI/CD Guide](https://www.react-native-harness.dev/docs/guides/ci-cd)
213+
- [Android Emulator Runner Action](https://github.com/ReactiveCircus/android-emulator-runner)
214+
- [iOS Simulator Action](https://github.com/futureware-tech/simulator-action)

.github/actions/setup-node-yarn/action.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ runs:
1818
shell: bash
1919
run: yarn install --immutable
2020

21-
- name: Prepare package
21+
- name: Cache built library
22+
id: cache-lib
23+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
24+
with:
25+
path: lib
26+
key: ${{ runner.os }}-lib-${{ hashFiles('src/**/*.{ts,tsx,js,jsx}', 'package.json', 'tsconfig.json') }}
27+
restore-keys: |
28+
${{ runner.os }}-lib-
29+
30+
- name: Build library (bob build)
31+
if: steps.cache-lib.outputs.cache-hit != 'true'
2232
shell: bash
2333
run: yarn prepare

.github/workflows/android.yml

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
name: Android Build & Test
2+
3+
on:
4+
workflow_call:
5+
6+
env:
7+
DEVICE_API_LEVEL: '34'
8+
DEVICE_ARCH: 'x86_64'
9+
DEVICE_TARGET: 'google_apis'
10+
AVD_NAME: 'Pixel_API_34'
11+
12+
jobs:
13+
android:
14+
name: Android Build & Test
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 60
17+
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
21+
22+
- name: Setup Project
23+
uses: ./.github/actions/setup-node-yarn
24+
25+
- name: Setup Java
26+
uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e
27+
with:
28+
distribution: 'zulu'
29+
java-version: '17'
30+
31+
- name: Setup Android SDK
32+
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407
33+
34+
- name: Enable KVM
35+
run: |
36+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
37+
sudo udevadm control --reload-rules
38+
sudo udevadm trigger --name-match=kvm
39+
ls /dev/kvm
40+
41+
# Check caches first
42+
- name: Cache APK build output
43+
id: cache-apk
44+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
45+
with:
46+
path: example/android/app/build/outputs/apk/debug/app-debug.apk
47+
key: ${{ runner.os }}-apk-${{ hashFiles('android/**/*.{java,kt,xml,gradle}', 'example/android/**/*.{java,kt,xml,gradle}', 'package.json', 'yarn.lock', '.github/workflows/android.yml') }}
48+
49+
- name: AVD cache
50+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
51+
id: avd-cache
52+
with:
53+
path: |
54+
~/.android/avd/*
55+
~/.android/adb*
56+
key: avd-${{ env.DEVICE_API_LEVEL }}-${{ env.DEVICE_ARCH }}
57+
58+
# Build dependencies (only if APK not cached)
59+
- name: Cache NDK
60+
id: cache-ndk
61+
if: steps.cache-apk.outputs.cache-hit != 'true'
62+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
63+
with:
64+
path: /usr/local/lib/android/sdk/ndk/27.3.13750724
65+
key: ${{ runner.os }}-ndk-27.3.13750724
66+
67+
- name: Install NDK
68+
if: steps.cache-apk.outputs.cache-hit != 'true' && steps.cache-ndk.outputs.cache-hit != 'true'
69+
run: echo "y" | ${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "ndk;27.3.13750724"
70+
71+
- name: Cache Gradle
72+
if: steps.cache-apk.outputs.cache-hit != 'true'
73+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
74+
with:
75+
path: |
76+
~/.gradle/caches
77+
~/.gradle/wrapper
78+
example/android/.gradle
79+
example/android/app/.cxx
80+
example/android/app/build/intermediates
81+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'android/build.gradle', 'example/android/app/build.gradle', '.github/workflows/android.yml') }}
82+
restore-keys: |
83+
${{ runner.os }}-gradle-
84+
85+
# Build APK (only if not cached)
86+
- name: Build APK with Gradle
87+
if: steps.cache-apk.outputs.cache-hit != 'true'
88+
working-directory: example
89+
run: yarn app:package:android
90+
91+
# Create AVD (only if not cached, runs after build)
92+
- name: Create AVD and generate snapshot for caching
93+
if: steps.avd-cache.outputs.cache-hit != 'true'
94+
uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b
95+
with:
96+
api-level: ${{ env.DEVICE_API_LEVEL }}
97+
target: ${{ env.DEVICE_TARGET }}
98+
arch: ${{ env.DEVICE_ARCH }}
99+
ram-size: 4096M
100+
disk-size: 6G
101+
force-avd-creation: false
102+
avd-name: ${{ env.AVD_NAME }}
103+
disable-animations: true
104+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096
105+
script: echo "Generated AVD snapshot for caching."
106+
107+
# Launch AVD, install app, and run tests
108+
- name: Run Harness E2E tests
109+
uses: reactivecircus/android-emulator-runner@b530d96654c385303d652368551fb075bc2f0b6b
110+
with:
111+
working-directory: example
112+
api-level: ${{ env.DEVICE_API_LEVEL }}
113+
target: ${{ env.DEVICE_TARGET }}
114+
arch: ${{ env.DEVICE_ARCH }}
115+
ram-size: 4096M
116+
force-avd-creation: false
117+
avd-name: ${{ env.AVD_NAME }}
118+
disable-animations: true
119+
emulator-boot-timeout: 900
120+
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -memory 4096
121+
script: |
122+
yarn app:install:android
123+
yarn harness:android
124+
125+
# Cleanup
126+
- name: Stop Gradle Daemon
127+
if: always()
128+
working-directory: example/android
129+
run: ./gradlew --stop

.github/workflows/build-android.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)