Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 57 additions & 41 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
name: Check

env:
# Version here should match the one in React Native template and packages/cmake-rn/src/cli.ts
NDK_VERSION: 27.1.12297006

on:
push:
branches:
Expand All @@ -9,41 +15,17 @@ concurrency:
cancel-in-progress: true

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/jod
- run: npm ci
- run: npm run lint
test-simple:
name: Run tests which doesn't require MacOS
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/jod
- run: npm ci
- run: npm run build
- run: npm test --workspace gyp-to-cmake --workspace cmake-rn --workspace react-native-node-api
test-windows:
name: Run tests on Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/jod
- run: npm ci
- run: npm run build
- run: npm test --workspace gyp-to-cmake --workspace cmake-rn --workspace react-native-node-api
test:
strategy:
fail-fast: false
matrix:
runner:
- ubuntu-latest
variant:
- android-tests

test-macos:
name: Run tests which requires MacOS
runs-on: macos-latest
name: Test (${{ matrix.variant }} on ${{ matrix.runner }})
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
Expand All @@ -56,11 +38,45 @@ jobs:
distribution: "temurin"
- name: Setup Android SDK
uses: android-actions/setup-android@v3
# Version here should match the one in React Native template and packages/cmake-rn/src/cli.ts
- run: sdkmanager --install "ndk;27.1.12297006"
with:
packages: tools platform-tools ndk;${{ env.NDK_VERSION }}
- run: npm ci
- run: npm run build
- run: npm run copy-node-api-headers --workspace react-native-node-api
- run: npm run build-weak-node-api --workspace react-native-node-api
- run: npm run generate-weak-node-api-injector --workspace react-native-node-api
- run: npm test --workspace @react-native-node-api/node-addon-examples
- name: Setup Android Emulator cache
if: matrix.variant == 'android-tests'
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: ${{ runner.os }}-avd-29
# See https://github.com/marketplace/actions/android-emulator-runner#running-hardware-accelerated-emulators-on-linux-runners
- name: Enable KVM group perms
if: matrix.runner == 'ubuntu-latest' && matrix.variant == 'android-tests'
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run tests (Android)
if: matrix.variant == 'android-tests'
timeout-minutes: 75
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
force-avd-creation: false
emulator-options: -no-snapshot-save -no-metrics -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
arch: ${{ runner.os == 'macOS' && 'arm64-v8a' || 'x86' }}
ndk: ${{ env.NDK_VERSION }}
cmake: 3.22.1
working-directory: apps/test-app
script: |
# Setup port forwarding to Mocha Remote
adb reverse tcp:8090 tcp:8090
# Uninstall the app if already in the snapshot (unlikely but could result in a signature mismatch failure)
adb uninstall com.microsoft.reacttestapp || true
# Build, install and run the app
npm run test:android
# Print some debug info
sleep 5
ps aux
93 changes: 55 additions & 38 deletions apps/test-app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,73 @@
import React from "react";
import { StyleSheet, Text, View, Button } from "react-native";
import { StyleSheet, View, SafeAreaView } from "react-native";

/* eslint-disable @typescript-eslint/no-require-imports -- We're using require to defer crashes */
import {
MochaRemoteProvider,
ConnectionText,
StatusEmoji,
StatusText,
} from "mocha-remote-react-native";

// import { requireNodeAddon } from "react-native-node-api";
import nodeAddonExamples from "react-native-node-addon-examples";
// import * as ferricExample from "ferric-example";
import nodeAddonExamples from "@react-native-node-api/node-addon-examples";

function App(): React.JSX.Element {
function loadTests() {
for (const [suiteName, examples] of Object.entries(nodeAddonExamples)) {
describe(suiteName, () => {
for (const [exampleName, requireExample] of Object.entries(examples)) {
it(exampleName, () => {
requireExample();
});
}
});
}

describe("ferric-example", () => {
it("exports a callable sum function", () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const exampleAddon = require("ferric-example");
const result = exampleAddon.sum(1, 3);
if (result !== 4) {
throw new Error(`Expected 1 + 3 to equal 4, but got ${result}`);
}
});
});
}

export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>React Native Node-API Modules</Text>
{Object.entries(nodeAddonExamples).map(([suiteName, examples]) => (
<View key={suiteName} style={styles.suite}>
<Text>{suiteName}</Text>
{Object.entries(examples).map(([exampleName, requireExample]) => (
<Button
key={exampleName}
title={exampleName}
onPress={requireExample}
/>
))}
<MochaRemoteProvider tests={loadTests}>
<SafeAreaView style={styles.container}>
<ConnectionText style={styles.connectionText} />
<View style={styles.statusContainer}>
<StatusEmoji style={styles.statusEmoji} />
<StatusText style={styles.statusText} />
</View>
))}
<View key="ferric-example" style={styles.suite}>
<Text>ferric-example</Text>
<Button
title={"Ferric Example: sum(1, 3)"}
onPress={() =>
console.log("1+3 = " + require("ferric-example").sum(1, 3))
}
/>
</View>
</View>
</SafeAreaView>
</MochaRemoteProvider>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
backgroundColor: "#fff",
},
statusContainer: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
suite: {
borderWidth: 1,
width: "96%",
margin: 10,
padding: 10,
statusEmoji: {
fontSize: 30,
margin: 30,
textAlign: "center",
},
title: {
statusText: {
fontSize: 20,
margin: 20,
textAlign: "center",
},
connectionText: {
textAlign: "center",
},
});

export default App;
15 changes: 12 additions & 3 deletions apps/test-app/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
{
"name": "react-native-node-api-test-app",
"type": "commonjs",
"private": true,
"version": "0.1.0",
"scripts": {
"android": "react-native run-android",
"start": "react-native start --no-interactive",
"android": "react-native run-android --no-packager --active-arch-only",
"build:android": "react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist/res",
"ios": "react-native run-ios",
"ios": "react-native run-ios --no-packager",
"pod-install": "cd ios && pod install",
"start": "react-native start"
"test:android:original": "mocha-remote --exit-on-error -- tsx ./scripts/run-tests.ts android",
"test:android": "mocha-remote --exit-on-error -- tsx ./scripts/fake-tests.ts",
"test:ios": "mocha-remote --exit-on-error -- tsx ./scripts/run-tests.ts ios"
},
"dependencies": {
"@babel/core": "^7.26.10",
Expand All @@ -20,8 +24,13 @@
"@react-native/metro-config": "0.79.0",
"@react-native/typescript-config": "0.79.0",
"@rnx-kit/metro-config": "^2.0.1",
"@types/mocha": "^10.0.10",
"@types/react": "^19.0.0",
"bufout": "^0.3.4",
"ferric-example": "^0.1.0",
"mocha": "^11.6.0",
"mocha-remote-cli": "^1.13.2",
"mocha-remote-react-native": "^1.13.2",
"react": "19.0.0",
"react-native": "0.79.1",
"react-native-node-addon-examples": "*",
Expand Down
42 changes: 42 additions & 0 deletions apps/test-app/scripts/fake-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import path from "node:path";

import { spawn, SpawnFailure } from "bufout";
import { Client } from "mocha-remote-client";

const cwd = path.resolve(__dirname, "..");
const env = {
...process.env,
FORCE_COLOR: "1",
};

const metro = spawn("react-native", ["start", "--no-interactive"], {
cwd,
stdio: "inherit",
outputPrefix: "[metro] ",
env,
});

process.once("SIGTERM", () => {
console.log("Received SIGTERM, shutting down metro server...");
metro.kill();
});

// Create a client, which will automatically connect to the server on the default port (8090)
const client = new Client({
// Called when the server asks the client to run
tests: () => {
// write your tests here or require a package that calls the mocha globals
describe("my thing", () => {
it("works", async () => {
// yay!
await new Promise((resolve) => setTimeout(resolve, 1000));
});
});
},
});

metro.catch((err) => {
if (!(err instanceof SpawnFailure)) {
throw err;
}
});
45 changes: 45 additions & 0 deletions apps/test-app/scripts/run-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import assert from "node:assert/strict";
import path from "node:path";

import { spawn } from "bufout";

// Ideally, we would just use "concurrently" or "npm-run-all" to run these in parallel but:
// - "concurrently" hangs the emulator action on Ubuntu
// - "npm-run-all" shows symptoms of not closing metro when Mocha Remote sends a SIGTERM

const platform = process.argv[2];
assert(
platform === "android" || platform === "ios",
"Platform must be 'android' or 'ios'"
);

const cwd = path.resolve(__dirname, "..");
const env = {
...process.env,
FORCE_COLOR: "1",
};

const metro = spawn("npx", ["react-native", "start", "--no-interactive"], {
cwd,
stdio: "inherit",
outputPrefix: "[metro] ",
env,
});

const build = spawn(
"npx",
[
"react-native",
`run-${platform}`,
"--no-packager",
...(platform === "android" ? ["--active-arch-only"] : []),
],
{
cwd,
stdio: "inherit",
outputPrefix: `[${platform}] `,
env,
}
);

Promise.all([metro, build]).catch(console.error);
7 changes: 6 additions & 1 deletion apps/test-app/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{
"extends": "@react-native/typescript-config/tsconfig.json"
"extends": "@react-native/typescript-config/tsconfig.json",
"compilerOptions": {
"types": ["react-native"]
},
"files": ["App.tsx", "index.ts"],
"references": [{ "path": "./tsconfig.node-scripts.json" }]
}
12 changes: 12 additions & 0 deletions apps/test-app/tsconfig.node-scripts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"compilerOptions": {
"composite": true,
"emitDeclarationOnly": true,
"outDir": "dist",
"rootDir": "scripts",
"types": ["node", "bufout", "mocha"]
},
"include": ["scripts/**/*.ts"],
"exclude": []
}
Loading