Skip to content

Commit eed67d1

Browse files
committed
feat: add react-native-harness CI integration for iOS
1 parent d2d975f commit eed67d1

10 files changed

Lines changed: 773 additions & 75 deletions

File tree

.github/workflows/ci.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,46 @@ jobs:
207207
TURBO_FORCE: ${{ steps.turbo-cache-ios.outputs.cache-hit != 'true' }}
208208
run: |
209209
yarn turbo run build:ios --cache-dir="${{ env.TURBO_CACHE_DIR }}"
210+
211+
test-harness-ios:
212+
needs: build-ios
213+
runs-on: macos-latest
214+
env:
215+
XCODE_VERSION: 16.4
216+
steps:
217+
- name: Checkout
218+
uses: actions/checkout@v4
219+
220+
- name: Setup
221+
uses: ./.github/actions/setup
222+
223+
- name: Use appropriate Xcode version
224+
uses: maxim-lobanov/setup-xcode@v1
225+
with:
226+
xcode-version: ${{ env.XCODE_VERSION }}
227+
228+
- name: Restore cocoapods
229+
id: cocoapods-cache
230+
uses: actions/cache/restore@v4
231+
with:
232+
path: |
233+
**/ios/Pods
234+
key: ${{ runner.os }}-cocoapods-${{ hashFiles('example/ios/Podfile.lock') }}
235+
restore-keys: |
236+
${{ runner.os }}-cocoapods-
237+
238+
- name: Install cocoapods
239+
if: steps.cocoapods-cache.outputs.cache-hit != 'true'
240+
run: |
241+
cd example
242+
bundle install
243+
bundle exec pod install --project-directory=ios
244+
245+
- name: Boot iOS Simulator
246+
run: |
247+
xcrun simctl boot "iPhone 16 Pro" || true
248+
249+
- name: Run harness tests on iOS
250+
run: |
251+
cd example
252+
yarn test:harness:ios

example/__tests__/rive.harness.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Harness tests for Rive - runs on real devices via react-native-harness.
3+
* Uses the same test suites as the in-app test runner.
4+
*/
5+
import { describe, it, beforeAll } from 'react-native-harness';
6+
import { RiveFileFactory } from '@rive-app/react-native';
7+
import type { RiveFile } from '@rive-app/react-native';
8+
import { harnessBackend } from '../src/testing/harnessBackend';
9+
import { allSuites } from '../src/testing/suites';
10+
11+
for (const suite of allSuites) {
12+
describe(suite.name, () => {
13+
let file: RiveFile;
14+
15+
beforeAll(async () => {
16+
file = await RiveFileFactory.fromSource(suite.riveAsset, undefined);
17+
});
18+
19+
const tests = suite.getTests(
20+
// Create a proxy that will access the file after beforeAll runs
21+
new Proxy({} as RiveFile, {
22+
get: (_target, prop) => {
23+
return (file as unknown as Record<string, unknown>)[prop as string];
24+
},
25+
}),
26+
harnessBackend
27+
);
28+
29+
for (const test of tests) {
30+
it(test.name, async () => {
31+
const result = await test.run();
32+
if (result.status === 'failed') {
33+
throw new Error(result.error);
34+
}
35+
});
36+
}
37+
});
38+
}

example/ios/Podfile.lock

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,7 +1880,7 @@ PODS:
18801880
- ReactCommon/turbomodule/core
18811881
- RNWorklets
18821882
- Yoga
1883-
- RNRive (0.1.2):
1883+
- RNRive (0.1.3):
18841884
- DoubleConversion
18851885
- glog
18861886
- hermes-engine
@@ -2234,76 +2234,76 @@ SPEC CHECKSUMS:
22342234
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
22352235
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
22362236
hermes-engine: 314be5250afa5692b57b4dd1705959e1973a8ebe
2237-
NitroModules: 0af9a8516f3d8f101976d60e1f34e2a22f401600
2237+
NitroModules: 7f50ee216f8403e8eb243acfc504f3f856d6914c
22382238
RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809
22392239
RCTDeprecation: 83ffb90c23ee5cea353bd32008a7bca100908f8c
22402240
RCTRequired: eb7c0aba998009f47a540bec9e9d69a54f68136e
22412241
RCTTypeSafety: 659ae318c09de0477fd27bbc9e140071c7ea5c93
22422242
React: c2d3aa44c49bb34e4dfd49d3ee92da5ebacc1c1c
22432243
React-callinvoker: 1bdfb7549b5af266d85757193b5069f60659ef9d
2244-
React-Core: 10597593fdbae06f0089881e025a172e51d4a769
2245-
React-CoreModules: 6907b255529dd46895cf687daa67b24484a612c2
2246-
React-cxxreact: a9f5b8180d6955bc3f6a3fcd657c4d9b4d95c1f6
2244+
React-Core: 7150cf9b6a5af063b37003062689f1691e79c020
2245+
React-CoreModules: 15a85e6665d61678942da6ae485b351f4c699049
2246+
React-cxxreact: 74f9de59259ac951923f5726aa14f0398f167af9
22472247
React-debug: e74e76912b91e08d580c481c34881899ccf63da9
2248-
React-defaultsnativemodule: 11f6ee2cf69bf3af9d0f28a6253def33d21b5266
2249-
React-domnativemodule: f940bbc4fa9e134190acbf3a4a9f95621b5a8f51
2250-
React-Fabric: 6f5c357bf3a42ff11f8844ad3fc7a1eb04f4b9de
2251-
React-FabricComponents: 10e0c0209822ac9e69412913a8af1ca33573379b
2252-
React-FabricImage: f582e764072dfa4715ae8c42979a5bace9cbcc12
2248+
React-defaultsnativemodule: 628285212bbd65417d40ad6a9f8781830fda6c98
2249+
React-domnativemodule: 185d9808198405c176784aaf33403d713bd24fb7
2250+
React-Fabric: c814804affbe1952e16149ddd20256e1bccae67e
2251+
React-FabricComponents: 81ef47d596966121784afec9924f9562a29b1691
2252+
React-FabricImage: f14f371d678aa557101def954ac3ba27e48948ff
22532253
React-featureflags: d5facceff8f8f6de430e0acecf4979a9a0839ba9
2254-
React-featureflagsnativemodule: a7dd141f1ef4b7c1331af0035689fbc742a49ff4
2255-
React-graphics: 36ae3407172c1c77cea29265d2b12b90aaef6aa0
2256-
React-hermes: 9116d4e6d07abeb519a2852672de087f44da8f12
2257-
React-idlecallbacksnativemodule: ae7f5ffc6cf2d2058b007b78248e5b08172ad5c3
2258-
React-ImageManager: 9daee0dc99ad6a001d4b9e691fbf37107e2b7b54
2259-
React-jserrorhandler: 1e6211581071edaf4ecd5303147328120c73f4dc
2260-
React-jsi: 753ba30c902f3a41fa7f956aca8eea3317a44ee6
2261-
React-jsiexecutor: 47520714aa7d9589c51c0f3713dfbfca4895d4f9
2262-
React-jsinspector: cfd27107f6d6f1076a57d88c932401251560fe5f
2263-
React-jsinspectortracing: 76a7d791f3c0c09a0d2bf6f46dfb0e79a4fcc0ac
2264-
React-jsitooling: 995e826570dd58f802251490486ebd3244a037ab
2265-
React-jsitracing: 094ae3d8c123cea67b50211c945b7c0443d3e97b
2266-
React-logger: 8edfcedc100544791cd82692ca5a574240a16219
2267-
React-Mapbuffer: c3f4b608e4a59dd2f6a416ef4d47a14400194468
2268-
React-microtasksnativemodule: 054f34e9b82f02bd40f09cebd4083828b5b2beb6
2269-
react-native-safe-area-context: 0b8555c40461feb7198e999912a3446602e7c601
2270-
React-NativeModulesApple: 2c4377e139522c3d73f5df582e4f051a838ff25e
2254+
React-featureflagsnativemodule: 96f0ab285382d95c90f663e02526a5ceefa95a11
2255+
React-graphics: 1a66ee0a3f093b125b853f6370296fadcaf6f233
2256+
React-hermes: 8b86e5f54a65ecb69cdf22b3a00a11562eda82d2
2257+
React-idlecallbacksnativemodule: 5c25ab145c602264d00cb26a397ab52e0efa031c
2258+
React-ImageManager: 15e34bd5ef1ac4a18e96660817ef70a7f99ee8c2
2259+
React-jserrorhandler: 02cdf2cd45350108be1ffd2b164578936dbbdff7
2260+
React-jsi: 6af1987cfbb1b6621664fdbf6c7b62bd4d38c923
2261+
React-jsiexecutor: 51f372998e0303585cb0317232b938d694663cbd
2262+
React-jsinspector: 3539ad976d073bfaa8a7d2fa9bef35e70e55033e
2263+
React-jsinspectortracing: e8dbacaf67c201f23052ca1c2bae2f7b84dec443
2264+
React-jsitooling: 95a34f41e3c249d42181de13b4f8d854f178ca9f
2265+
React-jsitracing: 25b029cf5cad488252d46da19dd8c4c134fd5fe4
2266+
React-logger: 368570a253f00879a1e4fea24ed4047e72e7bbf3
2267+
React-Mapbuffer: c04fcda1c6281fc0a6824c7dcc1633dd217ac1ec
2268+
React-microtasksnativemodule: ca2804a25fdcefffa0aa942aa23ab53b99614a34
2269+
react-native-safe-area-context: bc59472155ffb889a1ffe16c19a04c0cd451562b
2270+
React-NativeModulesApple: 452b86b29fae99ed0a4015dca3ad9cd222f88abf
22712271
React-oscompat: ef5df1c734f19b8003e149317d041b8ce1f7d29c
2272-
React-perflogger: 9a151e0b4c933c9205fd648c246506a83f31395d
2273-
React-performancetimeline: 5b0dfc0acba29ea0269ddb34cd6dd59d3b8a1c66
2272+
React-perflogger: 6fd2f6811533e9c19a61e855c3033eecbf4ad2a0
2273+
React-performancetimeline: abf31259d794c9274b3ea19c5016186925eec6c4
22742274
React-RCTActionSheet: a499b0d6d9793886b67ba3e16046a3fef2cdbbc3
2275-
React-RCTAnimation: cc64adc259aabc3354b73065e2231d796dfce576
2276-
React-RCTAppDelegate: 9d523da768f1c9e84c5f3b7e3624d097dfb0e16b
2277-
React-RCTBlob: e727f53eeefded7e6432eb76bd22b57bc880e5d1
2278-
React-RCTFabric: 58590aa4fdb4ad546c06a7449b486cf6844e991f
2279-
React-RCTFBReactNativeSpec: 9064c63d99e467a3893e328ba3612745c3c3a338
2280-
React-RCTImage: 7159cbdbb18a09d97ba1a611416eced75b3ccb29
2281-
React-RCTLinking: 46293afdb859bccc63e1d3dedc6901a3c04ef360
2282-
React-RCTNetwork: 4a6cd18f5bcd0363657789c64043123a896b1170
2283-
React-RCTRuntime: 5ab904fd749aa52f267ef771d265612582a17880
2284-
React-RCTSettings: 61e361dc85136d1cb0e148b7541993d2ee950ea7
2285-
React-RCTText: abd1e196c3167175e6baef18199c6d9d8ac54b4e
2286-
React-RCTVibration: 490e0dcb01a3fe4a0dfb7bc51ad5856d8b84f343
2275+
React-RCTAnimation: 2595dcb10a82216a511b54742f8c28d793852ac6
2276+
React-RCTAppDelegate: f03604b70f57c9469a84a159d8abecf793a5bcff
2277+
React-RCTBlob: e00f9b4e2f151938f4d9864cf33ebf24ac03328a
2278+
React-RCTFabric: 3945d116fd271598db262d4e6ed5691d431ed9e8
2279+
React-RCTFBReactNativeSpec: 0f4d4f0da938101f2ca9d5333a8f46e527ad2819
2280+
React-RCTImage: dac5e9f8ec476aefe6e60ee640ebc1dfaf1a4dbe
2281+
React-RCTLinking: 494b785a40d952a1dfbe712f43214376e5f0e408
2282+
React-RCTNetwork: b3d7c30cd21793e268db107dd0980cb61b3c1c44
2283+
React-RCTRuntime: a8ff419d437228e7b8a793b14f9d711e1cbb82af
2284+
React-RCTSettings: a060c7e381a3896104761b8eed7e284d95e37df3
2285+
React-RCTText: 4f272b72dbb61f390d8c8274528f9fdbff983806
2286+
React-RCTVibration: 0e5326220719aca12473d703aa46693e3b4ce67a
22872287
React-rendererconsistency: 351fdbc5c1fe4da24243d939094a80f0e149c7a1
2288-
React-renderercss: 3438814bee838ae7840a633ab085ac81699fd5cf
2289-
React-rendererdebug: 0ac2b9419ad6f88444f066d4b476180af311fb1e
2288+
React-renderercss: d333f2ada83969591100d91ec6b23ca2e17e1507
2289+
React-rendererdebug: 039e5949b72ba63c703de020701e3fd152434c61
22902290
React-rncore: 57ed480649bb678d8bdc386d20fee8bf2b0c307c
2291-
React-RuntimeApple: 8b7a9788f31548298ba1990620fe06b40de65ad7
2292-
React-RuntimeCore: e03d96fbd57ce69fd9bca8c925942194a5126dbc
2291+
React-RuntimeApple: 344a5e1105256000afabaa8df12c3e4cab880340
2292+
React-RuntimeCore: 0e48fb5e5160acc0334c7a723a42d42cef4b58b6
22932293
React-runtimeexecutor: d60846710facedd1edb70c08b738119b3ee2c6c2
2294-
React-RuntimeHermes: aab794755d9f6efd249b61f3af4417296904e3ba
2295-
React-runtimescheduler: c3cd124fa5db7c37f601ee49ca0d97019acd8788
2294+
React-RuntimeHermes: 064286a03871d932c99738e0f8ef854962ab4b99
2295+
React-runtimescheduler: e917ab17ae08c204af1ebf8f669b7e411b0220c8
22962296
React-timing: a90f4654cbda9c628614f9bee68967f1768bd6a5
2297-
React-utils: a612d50555b6f0f90c74b7d79954019ad47f5de6
2298-
ReactAppDependencyProvider: 04d5eb15eb46be6720e17a4a7fa92940a776e584
2299-
ReactCodegen: c63eda03ba1d94353fb97b031fc84f75a0d125ba
2300-
ReactCommon: 76d2dc87136d0a667678668b86f0fca0c16fdeb0
2297+
React-utils: 51c4e71608b8133fecc9a15801d244ae7bdf3758
2298+
ReactAppDependencyProvider: d5dcc564f129632276bd3184e60f053fcd574d6b
2299+
ReactCodegen: fda99a79c866370190e162083a35602fdc314e5d
2300+
ReactCommon: 4d0da92a5eb8da86c08e3ec34bd23ab439fb2461
23012301
RiveRuntime: ecde073222c6279c6f7f439132ac7f0a461b344b
2302-
RNCPicker: 28c076ae12a1056269ec0305fe35fac3086c477d
2303-
RNGestureHandler: 6b39f4e43e4b3a0fb86de9531d090ff205a011d5
2304-
RNReanimated: 66b68ebe3baf7ec9e716bd059d700726f250d344
2305-
RNRive: b056121a82044307a6f2030a9616f50a6cb6d9ec
2306-
RNWorklets: b1faafefb82d9f29c4018404a0fb33974b494a7b
2302+
RNCPicker: 83c74db2de8274d8a8f3e18d91dea174a708f8c4
2303+
RNGestureHandler: bff91bb5ab5688265c70f74180ef718b94f33fe3
2304+
RNReanimated: 9a24892f34ea317264883806d2e3de7ce34eab90
2305+
RNRive: ae2494d65061ffcc98a957e1966c9b16ee0d8ff2
2306+
RNWorklets: ddf16938b1ed7e878563a4fc8a690968ef3d27f1
23072307
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
23082308
Yoga: 9f110fc4b7aa538663cba3c14cbb1c335f43c13f
23092309

example/jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module.exports = {
2-
preset: 'react-native',
2+
preset: 'react-native-harness',
33
};

example/metro.config.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const path = require('path');
22
const { getDefaultConfig } = require('@react-native/metro-config');
33
const { getConfig } = require('react-native-builder-bob/metro-config');
4+
const { withRnHarness } = require('react-native-harness/metro');
45

56
const root = path.resolve(__dirname, '..');
67

@@ -13,7 +14,9 @@ config.transformer.unstable_allowRequireContext = true;
1314
*
1415
* @type {import('metro-config').MetroConfig}
1516
*/
16-
module.exports = getConfig(config, {
17-
root,
18-
project: __dirname,
19-
});
17+
module.exports = withRnHarness(
18+
getConfig(config, {
19+
root,
20+
project: __dirname,
21+
})
22+
);

example/package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"ios": "react-native run-ios",
88
"start": "react-native start",
99
"build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"",
10-
"build:ios": "react-native build-ios --mode Debug"
10+
"build:ios": "react-native build-ios --mode Debug",
11+
"test:harness:ios": "react-native-harness --harnessRunner ios",
12+
"test:harness:android": "react-native-harness --harnessRunner android"
1113
},
1214
"dependencies": {
1315
"@react-native-picker/picker": "^2.11.4",
@@ -28,14 +30,17 @@
2830
"@react-native-community/cli": "18.0.0",
2931
"@react-native-community/cli-platform-android": "18.0.0",
3032
"@react-native-community/cli-platform-ios": "18.0.0",
33+
"@react-native-harness/platform-android": "^1.0.0-alpha.20",
34+
"@react-native-harness/platform-apple": "^1.0.0-alpha.20",
3135
"@react-native/babel-preset": "0.79.2",
3236
"@react-native/metro-config": "0.79.2",
3337
"@react-native/typescript-config": "0.79.2",
3438
"@types/deep-equal": "^1.0.4",
3539
"@types/react": "^19.0.0",
3640
"babel-plugin-react-compiler": "^1.0.0",
3741
"deep-equal": "^2.2.3",
38-
"react-native-builder-bob": "^0.40.10"
42+
"react-native-builder-bob": "^0.40.10",
43+
"react-native-harness": "^1.0.0-alpha.20"
3944
},
4045
"engines": {
4146
"node": ">=18"

example/rn-harness.config.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { androidPlatform, androidEmulator } from '@react-native-harness/platform-android';
2+
import { applePlatform, appleSimulator } from '@react-native-harness/platform-apple';
3+
4+
export default {
5+
entryPoint: './index.js',
6+
appRegistryComponentName: 'RiveExample',
7+
runners: [
8+
androidPlatform({
9+
name: 'android',
10+
device: androidEmulator('Pixel_8_API_35'),
11+
bundleId: 'rive.example',
12+
}),
13+
applePlatform({
14+
name: 'ios',
15+
device: appleSimulator('iPhone 16 Pro', '18.0'),
16+
bundleId: 'rive.example',
17+
}),
18+
],
19+
defaultRunner: 'ios',
20+
};
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Assertion backend for react-native-harness CI tests.
3+
* Wraps Jest's expect() to implement AssertionBackend interface.
4+
*/
5+
import { expect } from 'react-native-harness';
6+
import type { AssertionBackend, JSType } from './types';
7+
8+
export const harnessBackend: AssertionBackend = {
9+
assertEqual<T>(actual: T, expected: T, _message: string): void {
10+
expect(actual).toEqual(expected);
11+
},
12+
13+
assertNotThrow(error: Error | undefined, _message: string): void {
14+
expect(error).toBeUndefined();
15+
},
16+
17+
assertThrow(
18+
error: Error | undefined,
19+
expectedMessage: string | undefined,
20+
_message: string
21+
): void {
22+
expect(error).toBeDefined();
23+
if (expectedMessage !== undefined) {
24+
expect(error?.message).toBe(expectedMessage);
25+
}
26+
},
27+
28+
assertType(value: unknown, expectedType: JSType, _message: string): void {
29+
expect(typeof value).toBe(expectedType);
30+
},
31+
32+
assertInstanceOf(
33+
value: unknown,
34+
constructor: new (...args: unknown[]) => unknown,
35+
_message: string
36+
): void {
37+
expect(value).toBeInstanceOf(constructor);
38+
},
39+
40+
assertContains(value: object, key: string, _message: string): void {
41+
expect(value).toHaveProperty(key);
42+
},
43+
44+
assertIsArray(value: unknown, _message: string): void {
45+
expect(Array.isArray(value)).toBe(true);
46+
},
47+
48+
assertStringContains(
49+
value: string,
50+
substring: string,
51+
_message: string
52+
): void {
53+
expect(value).toContain(substring);
54+
},
55+
56+
assertDefined(value: unknown, _message: string): void {
57+
expect(value).toBeDefined();
58+
},
59+
60+
assertUndefined(value: unknown, _message: string): void {
61+
expect(value).toBeUndefined();
62+
},
63+
};

example/src/testing/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export { State } from './State';
22
export { createTestRunner, type TestRunner } from './createTestRunner';
33
export { throwingBackend } from './throwingBackend';
4+
export { harnessBackend } from './harnessBackend';
45
export type {
56
AssertionBackend,
67
JSType,

0 commit comments

Comments
 (0)