Skip to content

Commit b4ae6be

Browse files
authored
feat: react-native android support (#777)
1 parent b215efd commit b4ae6be

File tree

8 files changed

+215
-6
lines changed

8 files changed

+215
-6
lines changed

md/github-actions.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ jobs:
4848
- uses: actions/checkout@v2
4949
- uses: microsoft/playwright-github-action@v1
5050
- run: npm install
51-
- run: npx aegir test -t browser -t webworker --bail --cov
51+
- run: npx aegir test -t browser --bail --cov
52+
- run: npx aegir test -t webworker --bail
5253
- uses: codecov/codecov-action@v1
5354
test-firefox:
5455
needs: check

md/react-native.md

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# React Native Setup
2+
3+
## Boring and heavy way for all OSes
4+
5+
Go to https://reactnative.dev/docs/environment-setup click in "React Native CLI Quickstart" and follow instructions.
6+
7+
If you don't want to fill your system with the full android studio or XCode follow the instructions below but be aware that there are dragons ahead!
8+
9+
## Android
10+
11+
```bash
12+
brew install --cask adoptopenjdk/openjdk/adoptopenjdk8
13+
export ANDROID_SDK_ROOT="~/android-sdk"
14+
touch ~/.android/repositories.cfg
15+
```
16+
17+
Download `cmdline-tool` from [https://developer.android.com/studio#cmdline-tools](https://developer.android.com/studio#cmdline-tools) and extract to `~/android-sdk/cmdline-tools/latest`
18+
19+
```bash
20+
21+
~/android-sdk/cmdline-tools/latest/bin/sdkmanager --update
22+
~/android-sdk/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-29" "build-tools;29.0.2" "system-images;android-29;default;x86_64"
23+
24+
// in your .zshrc or similar add sdk to PATH
25+
PATH=$PATH:$ANDROID_SDK_ROOT/emulator
26+
PATH=$PATH:$ANDROID_SDK_ROOT/tools
27+
PATH=$PATH:$ANDROID_SDK_ROOT/tools/bin
28+
PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools
29+
export PATH
30+
```
31+
Now all the tools are in the `$PATH` , no need for absolute paths anymore.
32+
33+
### Some examples
34+
35+
You normally dont need to run any of these
36+
37+
```bash
38+
# install new platforms, build tools and system images
39+
sdkmanager --update
40+
sdkmanager "platforms;android-29" "build-tools;29.0.2" "system-images;android-29;default;x86_64"
41+
42+
# create an avd
43+
avdmanager create avd -n aegir-android -d pixel --package "system-images;android-29;default;x86_64"
44+
45+
# delete avd
46+
avdmanager delete avd -n aegir-android
47+
48+
# manually run the emulator
49+
emulator @aegir-android
50+
51+
# list avds
52+
emulator -list-avds
53+
54+
# redirect port trafic
55+
adb -s emulator-5554 reverse tcp:3000 tcp:3000
56+
adb reverse --list
57+
adb reverse --remove-all
58+
```
59+
> If the internal aegir AVD changes SDK versions you might need to run the `sdkmanager` above to update and install the new SDK versions in your system.
60+
61+
62+
### emulator acceleration (optional)
63+
64+
[https://developer.android.com/studio/run/emulator-acceleration#vm-mac](https://developer.android.com/studio/run/emulator-acceleration#vm-mac)
65+
66+
### Aegir config
67+
Android needs special attention for networks settings ([docs](https://developer.android.com/studio/run/emulator-networking)). The most common change is to use the special address `10.0.2.2` to redirect trafic to your local loopback interface `127.0.0.1`.
68+
69+
```js
70+
module.exports = {
71+
test: {
72+
async before (options) {
73+
let echoServer = new EchoServer()
74+
await echoServer.start()
75+
const { address, port } = echoServer.server.address()
76+
let hostname = address // address will normally be 127.0.0.1
77+
78+
if(options.runner === 'react-native-android') {
79+
hostname = '10.0.2.2'
80+
}
81+
82+
return {
83+
echoServer,
84+
env: { ECHO_SERVER : format({ protocol: 'http:', hostname, port })}
85+
}
86+
},
87+
async after (options, before) {
88+
await before.echoServer.stop()
89+
}
90+
}
91+
}
92+
93+
94+
```

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"polka": "^0.5.2",
108108
"premove": "^3.0.1",
109109
"proper-lockfile": "^4.1.2",
110+
"react-native-test-runner": "^5.0.0",
110111
"read-pkg-up": "^7.0.1",
111112
"semver": "^7.3.5",
112113
"simple-git": "^2.37.0",

src/cmds/test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ module.exports = {
4343
alias: 't',
4444
describe: 'In which target environment to execute the tests',
4545
type: 'array',
46-
choices: ['node', 'browser', 'webworker', 'electron-main', 'electron-renderer'],
46+
choices: ['node', 'browser', 'webworker', 'electron-main', 'electron-renderer', 'react-native-android', 'react-native-ios'],
4747
default: userConfig.test.target
4848
},
4949
watch: {

src/test/index.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const pmap = require('p-map')
44
const node = require('./node')
55
const browser = require('./browser')
66
const electron = require('./electron')
7+
const rn = require('./react-native')
78

89
/**
910
* @typedef {import("execa").Options} ExecaOptions
@@ -71,8 +72,19 @@ const TASKS = [
7172
* @param {TestOptions & GlobalOptions} ctx
7273
*/
7374
enabled: (ctx) => ctx.target.includes('electron-renderer')
75+
},
76+
{
77+
title: 'Test React Native Android',
78+
/**
79+
* @param {TestOptions & GlobalOptions} opts
80+
* @param {ExecaOptions} execaOptions
81+
*/
82+
task: (opts, execaOptions) => rn({ ...opts, runner: 'react-native-android' }, execaOptions),
83+
/**
84+
* @param {TestOptions & GlobalOptions} ctx
85+
*/
86+
enabled: (ctx) => ctx.target.includes('react-native-android')
7487
}
75-
7688
]
7789

7890
module.exports = {

src/test/react-native.js

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
'use strict'
2+
const path = require('path')
3+
const execa = require('execa')
4+
const merge = require('merge-options')
5+
6+
/**
7+
* @typedef {import("execa").Options} ExecaOptions
8+
* @typedef {import('../types').TestOptions} TestOptions
9+
* @typedef {import('../types').GlobalOptions} GlobalOptions
10+
*/
11+
12+
/**
13+
*
14+
* @param {TestOptions & GlobalOptions} argv
15+
* @param {ExecaOptions} execaOptions
16+
*/
17+
module.exports = async (argv, execaOptions) => {
18+
const AVDName = 'aegir-android-29'
19+
const extra = argv['--'] ? argv['--'] : []
20+
const emulator = process.env.CI ? [] : ['--emulator', AVDName]
21+
const forwardOptions = /** @type {string[]} */([
22+
...extra,
23+
argv.timeout && `--timeout=${argv.timeout}`,
24+
argv.grep && `--grep=${argv.grep}`,
25+
argv.bail && '--bail'
26+
].filter(Boolean))
27+
const files = argv.files.length > 0
28+
? argv.files
29+
: [
30+
'**/*.spec.{js,ts}',
31+
'test/browser.{js,ts}'
32+
]
33+
34+
// before hook
35+
const before = await argv.fileConfig.test.before(argv)
36+
const beforeEnv = before && before.env ? before.env : {}
37+
38+
await checkAndroidEnv()
39+
40+
if (!await checkAvd(AVDName)) {
41+
await execa('avdmanager', [
42+
'create',
43+
'avd',
44+
'-n', AVDName,
45+
'-d', 'pixel',
46+
'--package', 'system-images;android-29;default;x86_64'
47+
])
48+
}
49+
50+
// run pw-test
51+
await execa('rn-test',
52+
[
53+
...files,
54+
'--platform', argv.runner === 'react-native-android' ? 'android' : 'ios',
55+
...emulator,
56+
'--reset-cache',
57+
...forwardOptions
58+
],
59+
merge(
60+
{
61+
env: {
62+
AEGIR_RUNNER: argv.runner,
63+
NODE_ENV: process.env.NODE_ENV || 'test',
64+
...beforeEnv
65+
},
66+
preferLocal: true,
67+
localDir: path.join(__dirname, '../..'),
68+
stdio: 'inherit'
69+
},
70+
execaOptions
71+
)
72+
)
73+
74+
// after hook
75+
await argv.fileConfig.test.after(argv, before)
76+
}
77+
78+
/**
79+
* Check for avd
80+
*
81+
* @param {string} name
82+
*/
83+
async function checkAvd (name) {
84+
const avd = await execa('emulator', ['-list-avds'])
85+
86+
return avd.stdout.split('\n').includes(name)
87+
}
88+
89+
async function checkAndroidEnv () {
90+
if (!process.env.ANDROID_SDK_ROOT) {
91+
throw new Error('ANDROID_SDK_ROOT is not set')
92+
}
93+
94+
try {
95+
await execa('emulator', ['-help'])
96+
// await execa('sdkmanager')
97+
await execa('avdmanager', ['list'])
98+
} catch (err) {
99+
throw new Error(`"Command ${err.path}" is not available, you need to properly setup your android environment.`)
100+
}
101+
}

src/types.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ interface TestOptions {
171171
/**
172172
* In which target environment to execute the tests
173173
*/
174-
target: Array<'node' | 'browser' | 'webworker' | 'electron-main' | 'electron-renderer'>
174+
target: Array<'node' | 'browser' | 'webworker' | 'electron-main' | 'electron-renderer' | 'react-native-android' | 'react-native-ios'>
175175
/**
176176
* Watch files for changes and rerun tests
177177
*/
@@ -203,7 +203,7 @@ interface TestOptions {
203203
/**
204204
* Runner enviroment
205205
*/
206-
runner: 'node' | 'browser' | 'webworker' | 'electron-main' | 'electron-renderer'
206+
runner: 'node' | 'browser' | 'webworker' | 'electron-main' | 'electron-renderer' | 'react-native-android' | 'react-native-ios'
207207
/**
208208
* Browser options
209209
*/

test/utils/echo-server.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('echo server spec', () => {
1515

1616
before(async () => {
1717
await echo.start()
18-
const { port, address } = /** @type {import('node:net').AddressInfo} */(echo.server.address())
18+
const { port, address } = /** @type {import('net').AddressInfo} */(echo.server.address())
1919
url = format({ protocol: 'http:', hostname: address, port })
2020
})
2121

0 commit comments

Comments
 (0)