diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..4989c12
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,131 @@
+name: React Native CI
+
+on:
+ pull_request:
+ branches: main
+ push:
+ branches: main
+ schedule:
+ - cron: '0 0 * * *' # Runs at 00:00 UTC every day
+
+jobs:
+ ios-build:
+ name: iOS Build
+ runs-on: macos-latest
+ defaults:
+ run:
+ working-directory: example
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: 'npm'
+ cache-dependency-path: example/package-lock.json
+
+ - name: Cache CocoaPods
+ uses: actions/cache@v4
+ with:
+ path: |
+ example/ios/Pods
+ key: ${{ runner.os }}-pods-${{ hashFiles('example/ios/Podfile.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-pods-
+
+ - name: Install dependencies
+ run: |
+ npm install --frozen-lockfile
+ cd ios && pod install
+
+ - name: Install Maestro CLI
+ run: |
+ curl -Ls "https://get.maestro.mobile.dev" | bash
+ brew tap facebook/fb
+ brew install facebook/fb/idb-companion
+
+ - name: Add Maestro to path
+ run: echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
+
+ - name: Start packager
+ run: npm start &
+
+ - name: Build iOS
+ run: |
+ npm run ios
+
+ - name: Setup iOS simulator
+ run: |
+ UDID=$(xcrun simctl list devices | grep "iPhone" | grep "Booted" | head -1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})")
+ if [ -z "$UDID" ]; then
+ UDID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})")
+ xcrun simctl boot "${UDID}"
+ fi
+ open -a Simulator
+ xcrun simctl launch "${UDID}" com.jscexample
+
+ - name: Run iOS tests
+ run: |
+ export MAESTRO_DRIVER_STARTUP_TIMEOUT=1500000
+ export MAESTRO_WAIT_TIMEOUT=10000
+ npm run test:e2e
+
+
+ android-build:
+ name: Android Build
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: example
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ cache: 'npm'
+ cache-dependency-path: example/package-lock.json
+
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '17'
+
+ - name: Install dependencies
+ run: npm install --frozen-lockfile
+
+ - name: Start packager
+ run: npm start &
+
+ - name: Install Maestro CLI
+ run: |
+ curl -Ls "https://get.maestro.mobile.dev" | bash
+
+ - name: Add Maestro to path
+ run: echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
+
+ - name: Create AVD and generate snapshot for caching
+ uses: reactivecircus/android-emulator-runner@v2
+ with:
+ target: aosp_atd
+ api-level: 30
+ arch: x86
+ ram-size: 4096M
+ channel: canary
+ profile: pixel
+ avd-name: Pixel_3a_API_30_AOSP
+ force-avd-creation: false
+ emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
+ emulator-boot-timeout: 12000
+ disable-animations: false
+ working-directory: example
+ script: |
+ npm run android
+ npm run test:e2e
diff --git a/example/App.tsx b/example/App.tsx
index 125fe1b..34b66f4 100644
--- a/example/App.tsx
+++ b/example/App.tsx
@@ -1,13 +1,6 @@
-/**
- * Sample React Native App
- * https://github.com/facebook/react-native
- *
- * @format
- */
-
-import React from 'react';
-import type {PropsWithChildren} from 'react';
+import React, { useState } from 'react';
import {
+ Button,
SafeAreaView,
ScrollView,
StatusBar,
@@ -16,52 +9,127 @@ import {
useColorScheme,
View,
} from 'react-native';
-
import {
Colors,
- DebugInstructions,
Header,
- LearnMoreLinks,
- ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';
-type SectionProps = PropsWithChildren<{
- title: string;
-}>;
-
-function Section({children, title}: SectionProps): React.JSX.Element {
- const isDarkMode = useColorScheme() === 'dark';
- return (
-
-
- {title}
-
-
- {children}
-
-
- );
-}
-
function App(): React.JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
+ const [testResult, setTestResult] = useState('No test run yet');
+ const [testName, setTestName] = useState('');
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
+ const testConsoleLog = () => {
+ console.log('Hello from JSC');
+ setTestName('Console Test Result');
+ setTestResult('Hello from JSC');
+ };
+
+ const testBasicOperations = () => {
+ const mathResult = 2 + 2;
+ const stringResult = 'Hello ' + 'World';
+ const arrayResult = [1, 2, 3].map(x => x * 2);
+
+ const result = `Math: ${mathResult}\nString: ${stringResult}\nArray: ${arrayResult}`;
+ console.log(result);
+ setTestName('Basic Operations Result');
+ setTestResult(result);
+ };
+
+ const testComplexOperations = () => {
+ const obj = { a: 1, b: 2 };
+ const square = (x: number) => x * x;
+ const squareResult = square(4);
+
+ let result = `Object: ${JSON.stringify(obj)}\nSquare(4): ${squareResult}`;
+
+ try {
+ // eslint-disable-next-line no-eval
+ const dynamicFn = eval('(x) => x * 3');
+ const dynamicResult = dynamicFn(4);
+ result += `\nDynamic function(4): ${dynamicResult}`;
+ } catch (error) {
+ result += `\nDynamic function error: ${error}`;
+ }
+
+ console.log(result);
+ setTestName('Complex Operations Result');
+ setTestResult(result);
+ };
+
+ const testGlobalAccess = () => {
+ const result = `SetTimeout exists: ${typeof global.setTimeout === 'function'}`;
+ console.log(result);
+ setTestName('Global Access Result');
+ setTestResult(result);
+ };
+
+ const testErrorHandling = () => {
+ let results: string[] = [];
+
+ try {
+ throw new Error('Custom error');
+ } catch (error) {
+ if (error instanceof Error) {
+ results.push(`Regular error: ${error.message}`);
+ }
+ }
+
+ try {
+ const undefined1 = undefined;
+ // @ts-ignore
+ undefined1.someMethod();
+ } catch (error) {
+ if (error instanceof Error) {
+ results.push(`Type error: ${error.message}`);
+ }
+ }
+
+ try {
+ // eslint-disable-next-line no-eval
+ eval('syntax error{');
+ } catch (error) {
+ if (error instanceof Error) {
+ results.push(`Eval error: ${error.message}`);
+ }
+ }
+
+ const result = results.join('\n');
+ console.log(result);
+ setTestName('Error Handling Result');
+ setTestResult(result);
+ };
+
+ const testAsync = async () => {
+ try {
+ const result = await new Promise((resolve) => {
+ setTimeout(() => resolve('Regular async completed'), 1000);
+ });
+ console.log('Regular async result:', result);
+ setTestName('Async Test Result');
+ setTestResult(String(result));
+ } catch (error) {
+ setTestName('Async Error');
+ setTestResult(String(error));
+ }
+ };
+
+ const testMemoryAndPerformance = () => {
+ const arr = new Array(1000000);
+ for (let i = 0; i < arr.length; i++) {
+ arr[i] = i;
+ }
+ const result = `Array length: ${arr.length}`;
+
+ console.log(result);
+ setTestName('Memory & Performance Result');
+ setTestResult(result);
+ };
+
return (
-
- Edit App.tsx to change this
- screen and then come back to see your edits.
-
-
-
-
- Read the docs to discover what to do next:
-
-
+ style={[
+ styles.container,
+ {backgroundColor: isDarkMode ? Colors.black : Colors.white},
+ ]}>
+
+
+
+
+
+
+
+
+
+ {testName || 'Test Results'}
+
+
+ {testResult}
+
+
@@ -97,21 +167,21 @@ function App(): React.JSX.Element {
}
const styles = StyleSheet.create({
- sectionContainer: {
- marginTop: 32,
- paddingHorizontal: 24,
+ container: {
+ padding: 12,
},
- sectionTitle: {
- fontSize: 24,
- fontWeight: '600',
+ resultContainer: {
+ marginTop: 20,
+ padding: 10,
+ backgroundColor: '#f0f0f0',
},
- sectionDescription: {
- marginTop: 8,
- fontSize: 18,
- fontWeight: '400',
+ resultTitle: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ marginBottom: 8,
},
- highlight: {
- fontWeight: '700',
+ resultContent: {
+ fontSize: 14,
},
});
diff --git a/example/ios/JSCExample.xcodeproj/project.pbxproj b/example/ios/JSCExample.xcodeproj/project.pbxproj
index 5099481..1e2805f 100644
--- a/example/ios/JSCExample.xcodeproj/project.pbxproj
+++ b/example/ios/JSCExample.xcodeproj/project.pbxproj
@@ -130,7 +130,7 @@
LastUpgradeCheck = 1210;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
- LastSwiftMigration = 1120;
+ LastSwiftMigration = 1600;
};
};
};
@@ -255,7 +255,7 @@
"-ObjC",
"-lc++",
);
- PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
+ PRODUCT_BUNDLE_IDENTIFIER = com.jscexample;
PRODUCT_NAME = JSCExample;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -282,7 +282,7 @@
"-ObjC",
"-lc++",
);
- PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
+ PRODUCT_BUNDLE_IDENTIFIER = com.jscexample;
PRODUCT_NAME = JSCExample;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -363,10 +363,7 @@
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
- OTHER_LDFLAGS = (
- "$(inherited)",
- " ",
- );
+ OTHER_LDFLAGS = "$(inherited) ";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -440,10 +437,7 @@
"-DFOLLY_CFG_NO_COROUTINES=1",
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
);
- OTHER_LDFLAGS = (
- "$(inherited)",
- " ",
- );
+ OTHER_LDFLAGS = "$(inherited) ";
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = false;
diff --git a/example/ios/JSCExample/Info.plist b/example/ios/JSCExample/Info.plist
index 5bc06a7..709fb5b 100644
--- a/example/ios/JSCExample/Info.plist
+++ b/example/ios/JSCExample/Info.plist
@@ -26,7 +26,6 @@
NSAppTransportSecurity
-
NSAllowsArbitraryLoads
NSAllowsLocalNetworking
diff --git a/example/maestro-tests/base.yaml b/example/maestro-tests/base.yaml
new file mode 100644
index 0000000..3530187
--- /dev/null
+++ b/example/maestro-tests/base.yaml
@@ -0,0 +1,56 @@
+appId: com.jscexample
+---
+- launchApp:
+ appId: com.jscexample
+- takeScreenshot: MainScreen
+- assertVisible:
+ text: "Welcome to React Native"
+
+# Console Log Test
+- tapOn: "Console Log Test"
+- assertVisible:
+ text: "Console Test Result"
+- assertVisible:
+ text: "Hello from JSC"
+
+# Basic Operations
+- tapOn: "Basic Operations"
+- assertVisible:
+ text: "Basic Operations Result"
+- assertVisible:
+ text: "Math: 4\nString: Hello World\nArray: 2,4,6"
+
+# Complex Operations
+- tapOn: "Complex Operations"
+- assertVisible:
+ text: "Complex Operations Result"
+- assertVisible:
+ text: "Object: {\"a\":1,\"b\":2}\nSquare(4): 16\nDynamic function(4): 12"
+
+# Global Access Test
+- tapOn: "Global Access Test"
+- assertVisible:
+ text: "Global Access Result"
+- assertVisible:
+ text: "SetTimeout exists: true"
+
+# Error Handling Test
+- tapOn: "Error Handling Test"
+- assertVisible:
+ text: "Error Handling Result"
+- assertVisible:
+ text: "Regular error: Custom error\nType error: undefined is not an object (evaluating 'undefined1.someMethod')\nEval error: Unexpected identifier 'error'"
+
+# Async Test
+- tapOn: "Async Test"
+- assertVisible:
+ text: "Async Test Result"
+- assertVisible:
+ text: "Regular async completed"
+
+# Memory & Performance Test
+- tapOn: "Memory & Performance"
+- assertVisible:
+ text: "Memory & Performance Result"
+- assertVisible:
+ text: "Array length: 1000000"
diff --git a/example/package-lock.json b/example/package-lock.json
index a98da2e..70600ab 100644
--- a/example/package-lock.json
+++ b/example/package-lock.json
@@ -7,6 +7,7 @@
"": {
"name": "JSCExample",
"version": "0.0.1",
+ "hasInstallScript": true,
"dependencies": {
"react": "19.0.0",
"react-native": "nightly"
diff --git a/example/package.json b/example/package.json
index 54204af..abb0a08 100644
--- a/example/package.json
+++ b/example/package.json
@@ -3,11 +3,16 @@
"version": "0.0.1",
"private": true,
"scripts": {
- "android": "react-native run-android",
- "ios": "react-native run-ios",
+ "android": "react-native run-android --mode release",
+ "ios": "react-native run-ios --mode Release",
"lint": "eslint .",
"start": "react-native start",
- "test": "jest"
+ "test": "jest",
+ "mkdist": "node -e \"require('node:fs').mkdirSync('dist', { recursive: true, mode: 0o755 })\"",
+ "build:android": "npm run mkdist && react-native bundle --entry-file index.js --platform android --bundle-output dist/main.android.jsbundle --assets-dest dist/res --dev false",
+ "build:ios": "npm run mkdist && react-native bundle --entry-file index.js --platform ios --bundle-output dist/main.ios.jsbundle --assets-dest dist --dev false",
+ "test:e2e": "maestro test maestro-tests",
+ "postinstall": "npx patch-package"
},
"dependencies": {
"react": "19.0.0",