expo-dev-client 6.0.17-canary-20251031-b135dff → 6.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -4
- package/android/build.gradle +14 -14
- package/e2e/DevLauncher.e2e.ts +138 -0
- package/e2e/android/DetoxTest.java +92 -0
- package/e2e/android/MainActivity.java +40 -0
- package/e2e/android/MainApplication.java +84 -0
- package/e2e/app/App.tsx +19 -0
- package/e2e/config.json +9 -0
- package/e2e/environments.js +23 -0
- package/e2e/ios/AppDelegate.m +100 -0
- package/package.json +11 -10
package/CHANGELOG.md
CHANGED
|
@@ -10,19 +10,23 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
-
## 6.0.
|
|
13
|
+
## 6.0.17 — 2025-11-05
|
|
14
14
|
|
|
15
15
|
_This version does not introduce any user-facing changes._
|
|
16
16
|
|
|
17
|
-
## 6.0.
|
|
17
|
+
## 6.0.16 — 2025-10-21
|
|
18
18
|
|
|
19
19
|
_This version does not introduce any user-facing changes._
|
|
20
20
|
|
|
21
|
-
## 6.0.
|
|
21
|
+
## 6.0.15 — 2025-10-10
|
|
22
22
|
|
|
23
23
|
_This version does not introduce any user-facing changes._
|
|
24
24
|
|
|
25
|
-
## 6.0.
|
|
25
|
+
## 6.0.14 — 2025-10-09
|
|
26
|
+
|
|
27
|
+
_This version does not introduce any user-facing changes._
|
|
28
|
+
|
|
29
|
+
## 6.0.13 — 2025-10-01
|
|
26
30
|
|
|
27
31
|
_This version does not introduce any user-facing changes._
|
|
28
32
|
|
package/android/build.gradle
CHANGED
|
@@ -8,13 +8,13 @@ expoModule {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
group = "host.exp.exponent"
|
|
11
|
-
version = "6.0.17
|
|
11
|
+
version = "6.0.17"
|
|
12
12
|
|
|
13
13
|
android {
|
|
14
14
|
namespace "expo.modules.devclient"
|
|
15
15
|
defaultConfig {
|
|
16
16
|
versionCode 1
|
|
17
|
-
versionName "6.0.17
|
|
17
|
+
versionName "6.0.17"
|
|
18
18
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -46,28 +46,28 @@ dependencies {
|
|
|
46
46
|
androidTestImplementation 'com.facebook.react:react-android'
|
|
47
47
|
androidTestImplementation 'com.facebook.react:hermes-android'
|
|
48
48
|
|
|
49
|
-
androidTestImplementation('androidx.test.espresso:espresso-core:3.
|
|
50
|
-
androidTestImplementation('androidx.test:core:1.
|
|
51
|
-
androidTestImplementation('androidx.test:core-ktx:1.
|
|
52
|
-
androidTestImplementation('androidx.test.ext:junit:1.
|
|
53
|
-
androidTestImplementation('androidx.test.ext:junit-ktx:1.
|
|
54
|
-
androidTestImplementation('androidx.test:runner:1.
|
|
55
|
-
androidTestImplementation('androidx.test:rules:1.
|
|
49
|
+
androidTestImplementation('androidx.test.espresso:espresso-core:3.6.1')
|
|
50
|
+
androidTestImplementation('androidx.test:core:1.6.1')
|
|
51
|
+
androidTestImplementation('androidx.test:core-ktx:1.6.1')
|
|
52
|
+
androidTestImplementation('androidx.test.ext:junit:1.2.1')
|
|
53
|
+
androidTestImplementation('androidx.test.ext:junit-ktx:1.2.1')
|
|
54
|
+
androidTestImplementation('androidx.test:runner:1.6.2')
|
|
55
|
+
androidTestImplementation('androidx.test:rules:1.6.1')
|
|
56
56
|
|
|
57
57
|
androidTestImplementation 'org.webkit:android-jsc:+'
|
|
58
58
|
|
|
59
59
|
androidTestImplementation "io.insert-koin:koin-test:3.1.2"
|
|
60
60
|
androidTestImplementation "io.insert-koin:koin-test-junit4:3.1.2"
|
|
61
61
|
|
|
62
|
-
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.
|
|
63
|
-
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.
|
|
62
|
+
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
|
|
63
|
+
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"
|
|
64
64
|
|
|
65
|
-
androidTestImplementation "androidx.appcompat:appcompat:1.7.
|
|
65
|
+
androidTestImplementation "androidx.appcompat:appcompat:1.7.0"
|
|
66
66
|
|
|
67
|
+
androidTestImplementation "com.google.truth:truth:1.1.2"
|
|
67
68
|
androidTestImplementation 'io.mockk:mockk-android:1.13.11'
|
|
68
|
-
androidTestImplementation "com.google.truth:truth:1.4.5"
|
|
69
69
|
|
|
70
70
|
// Fixes "e: java.lang.AssertionError: No such enum entry LIBRARY_GROUP_PREFIX in org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl@b254b575"
|
|
71
71
|
// According to the https://stackoverflow.com/a/67736351
|
|
72
|
-
implementation 'androidx.annotation:annotation:1.
|
|
72
|
+
implementation 'androidx.annotation:annotation:1.2.0'
|
|
73
73
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { element, expect, waitFor, by, device } from 'detox';
|
|
2
|
+
|
|
3
|
+
const LauncherMainScreenTimeout = 100 * 1000;
|
|
4
|
+
const MenuTimeout = 100 * 1000;
|
|
5
|
+
const LocalAppTimeout = 160 * 1000;
|
|
6
|
+
|
|
7
|
+
const sleep = (duration: number) =>
|
|
8
|
+
new Promise<void>((resolve) => setTimeout(() => resolve(), duration));
|
|
9
|
+
|
|
10
|
+
async function pressElementByString(buttonString: string) {
|
|
11
|
+
const button = element(by.text(buttonString));
|
|
12
|
+
await expect(button).toBeVisible();
|
|
13
|
+
await tapButton(button);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function pressMenuElementByString(buttonString: string, timeout: number = 0) {
|
|
17
|
+
let button = element(by.text(buttonString));
|
|
18
|
+
|
|
19
|
+
await waitFor(button).toBeVisible().withTimeout(timeout);
|
|
20
|
+
|
|
21
|
+
// When we open the dev-menu, we will see an animation.
|
|
22
|
+
// Unfortunately, if we try to click to button before the
|
|
23
|
+
// animation finishes, it may not work. We might click different a button.
|
|
24
|
+
// So try to wait for the animation to finish.
|
|
25
|
+
await sleep(1000);
|
|
26
|
+
|
|
27
|
+
button = element(by.text(buttonString));
|
|
28
|
+
await waitFor(button).toBeVisible();
|
|
29
|
+
|
|
30
|
+
await tapButton(button);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function runWithoutSynchronization(block: () => Promise<void>) {
|
|
34
|
+
await device.disableSynchronization();
|
|
35
|
+
await block();
|
|
36
|
+
await device.enableSynchronization();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getInvocationManager() {
|
|
40
|
+
// @ts-ignore
|
|
41
|
+
return global.detox[Object.getOwnPropertySymbols(global.detox)[0]]._invocationManager;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getLocalIPAddress(): string {
|
|
45
|
+
return require('os')
|
|
46
|
+
.networkInterfaces()
|
|
47
|
+
.en0.find((elm: { family: string }) => elm.family === 'IPv4').address;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function openMenu(): Promise<void> {
|
|
51
|
+
if (device.getPlatform() === 'android') {
|
|
52
|
+
return await getInvocationManager().execute({
|
|
53
|
+
target: {
|
|
54
|
+
type: 'Class',
|
|
55
|
+
value: 'com.testrunner.DevClientDetoxHelper',
|
|
56
|
+
},
|
|
57
|
+
method: 'openMenu',
|
|
58
|
+
args: [],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return await device.shake();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function waitForLauncherMainScreen() {
|
|
65
|
+
await waitFor(element(by.id('DevLauncherMainScreen')))
|
|
66
|
+
.toBeVisible()
|
|
67
|
+
.withTimeout(LauncherMainScreenTimeout);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function waitForAppMainScreen() {
|
|
71
|
+
await waitFor(element(by.id('LocalAppMainScreen')))
|
|
72
|
+
.toBeVisible()
|
|
73
|
+
.withTimeout(LocalAppTimeout);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function ensureThatLauncherMainScreenIsVisible() {
|
|
77
|
+
if (device.getPlatform() === 'ios') {
|
|
78
|
+
await expect(element(by.id('DevLauncherMainScreen'))).toBeVisible();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await waitForLauncherMainScreen();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function tapButton(button: Detox.IndexableNativeElement) {
|
|
86
|
+
// We have to make 2 tap - it is a bug in React Native.
|
|
87
|
+
await button.multiTap(2);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe('DevLauncher', () => {
|
|
91
|
+
beforeEach(async () => {
|
|
92
|
+
await device.launchApp({ newInstance: true });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should render main screen', async () => {
|
|
96
|
+
await ensureThatLauncherMainScreenIsVisible();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should be able to go to the settings screen and come back to the main screen', async () => {
|
|
100
|
+
await ensureThatLauncherMainScreenIsVisible();
|
|
101
|
+
|
|
102
|
+
await pressElementByString('Settings');
|
|
103
|
+
await expect(element(by.id('DevLauncherSettingsScreen'))).toBeVisible();
|
|
104
|
+
|
|
105
|
+
await pressElementByString('Home');
|
|
106
|
+
await expect(element(by.id('DevLauncherMainScreen'))).toBeVisible();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should be able to load app from URL and come back to the launcher screen', async () => {
|
|
110
|
+
await ensureThatLauncherMainScreenIsVisible();
|
|
111
|
+
|
|
112
|
+
const urlToggle = element(by.id('DevLauncherURLToggle'));
|
|
113
|
+
const urlInput = element(by.id('DevLauncherURLInput'));
|
|
114
|
+
const loadButton = element(by.id('DevLauncherLoadAppButton'));
|
|
115
|
+
|
|
116
|
+
await expect(urlToggle).toBeVisible();
|
|
117
|
+
await urlToggle.tap();
|
|
118
|
+
|
|
119
|
+
await expect(urlInput).toBeVisible();
|
|
120
|
+
await expect(loadButton).toBeVisible();
|
|
121
|
+
|
|
122
|
+
await urlInput.typeText(`http://${getLocalIPAddress()}:8081`);
|
|
123
|
+
if (device.getPlatform() === 'android') {
|
|
124
|
+
// close keyboard
|
|
125
|
+
await device.pressBack();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await runWithoutSynchronization(async () => {
|
|
129
|
+
await tapButton(loadButton);
|
|
130
|
+
await waitForAppMainScreen();
|
|
131
|
+
await openMenu();
|
|
132
|
+
|
|
133
|
+
await pressMenuElementByString('Go home', MenuTimeout);
|
|
134
|
+
|
|
135
|
+
await waitForLauncherMainScreen();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
package com.testrunner;
|
|
2
|
+
|
|
3
|
+
import static androidx.test.espresso.Espresso.onView;
|
|
4
|
+
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
|
5
|
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
|
6
|
+
|
|
7
|
+
import android.app.Activity;
|
|
8
|
+
import android.content.Context;
|
|
9
|
+
import android.content.ContextWrapper;
|
|
10
|
+
import android.view.View;
|
|
11
|
+
import android.view.ViewGroup;
|
|
12
|
+
|
|
13
|
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
14
|
+
import androidx.test.filters.LargeTest;
|
|
15
|
+
import androidx.test.rule.ActivityTestRule;
|
|
16
|
+
|
|
17
|
+
import com.facebook.react.ReactApplication;
|
|
18
|
+
import com.facebook.react.ReactNativeHost;
|
|
19
|
+
import com.wix.detox.Detox;
|
|
20
|
+
import com.wix.detox.config.DetoxConfig;
|
|
21
|
+
|
|
22
|
+
import org.junit.Rule;
|
|
23
|
+
import org.junit.Test;
|
|
24
|
+
import org.junit.runner.RunWith;
|
|
25
|
+
|
|
26
|
+
import expo.modules.devlauncher.DevLauncherController;
|
|
27
|
+
import expo.modules.devlauncher.launcher.DevLauncherActivity;
|
|
28
|
+
import expo.modules.devmenu.DevMenuManager;
|
|
29
|
+
|
|
30
|
+
// We need this class to pass dev launcher host to detox.
|
|
31
|
+
// Otherwise it won't detect that the app has been started.
|
|
32
|
+
class ReactNativeHolder extends ContextWrapper implements ReactApplication {
|
|
33
|
+
public ReactNativeHolder(Context base) {
|
|
34
|
+
super(base);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Override
|
|
38
|
+
public ReactNativeHost getReactNativeHost() {
|
|
39
|
+
return DevLauncherController.getInstance().getDevClientHost();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class DevClientDetoxHelper {
|
|
44
|
+
public static Activity getCurrentActivity() {
|
|
45
|
+
final Activity[] activity = new Activity[1];
|
|
46
|
+
|
|
47
|
+
onView(isRoot()).check((view, noViewFoundException) -> {
|
|
48
|
+
|
|
49
|
+
View checkedView = view;
|
|
50
|
+
|
|
51
|
+
while (checkedView instanceof ViewGroup && ((ViewGroup) checkedView).getChildCount() > 0) {
|
|
52
|
+
|
|
53
|
+
checkedView = ((ViewGroup) checkedView).getChildAt(0);
|
|
54
|
+
|
|
55
|
+
if (checkedView.getContext() instanceof Activity) {
|
|
56
|
+
activity[0] = (Activity) checkedView.getContext();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return activity[0];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static void openMenu() throws InterruptedException {
|
|
65
|
+
getInstrumentation().waitForIdleSync();
|
|
66
|
+
Activity activity = getCurrentActivity();
|
|
67
|
+
int counter = 10;
|
|
68
|
+
while (counter-- > 0 && activity == null) {
|
|
69
|
+
Thread.sleep(100);
|
|
70
|
+
activity = getCurrentActivity();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
DevMenuManager.INSTANCE.openMenu(activity, null);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@RunWith(AndroidJUnit4.class)
|
|
78
|
+
@LargeTest
|
|
79
|
+
public class DetoxTest {
|
|
80
|
+
@Rule
|
|
81
|
+
public ActivityTestRule<DevLauncherActivity> mActivityRule = new ActivityTestRule<>(DevLauncherActivity.class, false, false);
|
|
82
|
+
|
|
83
|
+
@Test
|
|
84
|
+
public void runDetoxTests() {
|
|
85
|
+
DetoxConfig detoxConfig = new DetoxConfig();
|
|
86
|
+
detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
|
|
87
|
+
detoxConfig.rnContextLoadTimeoutSec = 180;
|
|
88
|
+
|
|
89
|
+
ReactNativeHolder reactNativeHolder = new ReactNativeHolder(getInstrumentation().getTargetContext().getApplicationContext());
|
|
90
|
+
Detox.runTests(mActivityRule, reactNativeHolder, detoxConfig);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// custom
|
|
2
|
+
package com.testrunner;
|
|
3
|
+
|
|
4
|
+
import android.content.Intent;
|
|
5
|
+
|
|
6
|
+
import expo.modules.devmenu.react.DevMenuAwareReactActivity;
|
|
7
|
+
import com.facebook.react.ReactActivityDelegate;
|
|
8
|
+
|
|
9
|
+
import expo.modules.ReactActivityDelegateWrapper;
|
|
10
|
+
import expo.modules.devlauncher.DevLauncherController;
|
|
11
|
+
|
|
12
|
+
public class MainActivity extends DevMenuAwareReactActivity {
|
|
13
|
+
/**
|
|
14
|
+
* Returns the name of the main component registered from JavaScript.
|
|
15
|
+
* This is used to schedule rendering of the component.
|
|
16
|
+
*/
|
|
17
|
+
@Override
|
|
18
|
+
protected String getMainComponentName() {
|
|
19
|
+
return "main";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@Override
|
|
23
|
+
public void onNewIntent(Intent intent) {
|
|
24
|
+
if (DevLauncherController.tryToHandleIntent(this, intent)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
super.onNewIntent(intent);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Override
|
|
31
|
+
protected ReactActivityDelegate createReactActivityDelegate() {
|
|
32
|
+
return DevLauncherController.wrapReactActivityDelegate(this, () -> new ReactActivityDelegateWrapper(
|
|
33
|
+
this,
|
|
34
|
+
new ReactActivityDelegate(
|
|
35
|
+
this,
|
|
36
|
+
getMainComponentName()
|
|
37
|
+
)
|
|
38
|
+
));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package com.testrunner;
|
|
2
|
+
|
|
3
|
+
import android.app.Application;
|
|
4
|
+
import android.content.res.Configuration;
|
|
5
|
+
|
|
6
|
+
import androidx.annotation.NonNull;
|
|
7
|
+
import androidx.annotation.Nullable;
|
|
8
|
+
|
|
9
|
+
import com.facebook.react.PackageList;
|
|
10
|
+
import com.facebook.react.ReactApplication;
|
|
11
|
+
import com.facebook.react.ReactNativeHost;
|
|
12
|
+
import com.facebook.react.ReactPackage;
|
|
13
|
+
import com.facebook.soloader.SoLoader;
|
|
14
|
+
|
|
15
|
+
import expo.interfaces.devmenu.DevMenuPreferencesInterface;
|
|
16
|
+
import expo.modules.ApplicationLifecycleDispatcher;
|
|
17
|
+
import expo.modules.ReactNativeHostWrapper;
|
|
18
|
+
import expo.modules.devlauncher.DevLauncherController;
|
|
19
|
+
import expo.modules.devmenu.DevMenuDefaultPreferences;
|
|
20
|
+
import expo.modules.devmenu.DevMenuManager;
|
|
21
|
+
import expo.modules.devmenu.tests.DevMenuTestInterceptor;
|
|
22
|
+
|
|
23
|
+
import java.util.List;
|
|
24
|
+
|
|
25
|
+
public class MainApplication extends Application implements ReactApplication {
|
|
26
|
+
private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(
|
|
27
|
+
this,
|
|
28
|
+
new ReactNativeHost(this) {
|
|
29
|
+
@Override
|
|
30
|
+
public boolean getUseDeveloperSupport() {
|
|
31
|
+
return BuildConfig.DEBUG;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Override
|
|
35
|
+
protected List<ReactPackage> getPackages() {
|
|
36
|
+
return new PackageList(this).getPackages();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Override
|
|
40
|
+
protected String getJSMainModuleName() {
|
|
41
|
+
return ".expo/.virtual-metro-entry";
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
@Override
|
|
46
|
+
public ReactNativeHost getReactNativeHost() {
|
|
47
|
+
return mReactNativeHost;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Override
|
|
51
|
+
public void onCreate() {
|
|
52
|
+
super.onCreate();
|
|
53
|
+
SoLoader.init(this, /* native exopackage */ false);
|
|
54
|
+
|
|
55
|
+
DevMenuManager.INSTANCE.setTestInterceptor(new DevMenuTestInterceptor() {
|
|
56
|
+
@Nullable
|
|
57
|
+
@Override
|
|
58
|
+
public DevMenuPreferencesInterface overrideSettings() {
|
|
59
|
+
return new DevMenuDefaultPreferences() {
|
|
60
|
+
@Override
|
|
61
|
+
public boolean getShowsAtLaunch() {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Override
|
|
66
|
+
public boolean isOnboardingFinished() {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
DevLauncherController.initialize(this, mReactNativeHost);
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
ApplicationLifecycleDispatcher.onApplicationCreate(this);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Override
|
|
80
|
+
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
|
81
|
+
super.onConfigurationChanged(newConfig);
|
|
82
|
+
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/e2e/app/App.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export default function App() {
|
|
5
|
+
return (
|
|
6
|
+
<View style={styles.container} testID="LocalAppMainScreen">
|
|
7
|
+
<Text>Open up App.js to start working on your app!</Text>
|
|
8
|
+
</View>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const styles = StyleSheet.create({
|
|
13
|
+
container: {
|
|
14
|
+
flex: 1,
|
|
15
|
+
backgroundColor: '#fff',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
justifyContent: 'center',
|
|
18
|
+
},
|
|
19
|
+
});
|
package/e2e/config.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const {
|
|
2
|
+
DetoxCircusEnvironment,
|
|
3
|
+
SpecReporter,
|
|
4
|
+
WorkerAssignReporter,
|
|
5
|
+
} = require('detox/runners/jest-circus');
|
|
6
|
+
|
|
7
|
+
class CustomDetoxEnvironment extends DetoxCircusEnvironment {
|
|
8
|
+
constructor(config, context) {
|
|
9
|
+
super(config, context);
|
|
10
|
+
|
|
11
|
+
// Can be safely removed, if you are content with the default value (=300000ms)
|
|
12
|
+
this.initTimeout = 300000;
|
|
13
|
+
|
|
14
|
+
// This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level.
|
|
15
|
+
// This is strictly optional.
|
|
16
|
+
this.registerListeners({
|
|
17
|
+
SpecReporter,
|
|
18
|
+
WorkerAssignReporter,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = CustomDetoxEnvironment;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#import "AppDelegate.h"
|
|
2
|
+
|
|
3
|
+
#import <React/RCTBridge.h>
|
|
4
|
+
#import <React/RCTBundleURLProvider.h>
|
|
5
|
+
#import <React/RCTRootView.h>
|
|
6
|
+
#import <React/RCTLinkingManager.h>
|
|
7
|
+
|
|
8
|
+
@import ExpoModulesCore;
|
|
9
|
+
@import EXDevMenu;
|
|
10
|
+
|
|
11
|
+
#import <EXDevLauncher/EXDevLauncherController.h>
|
|
12
|
+
|
|
13
|
+
@interface DevMenuDetoxTestInterceptor : NSObject<DevMenuTestInterceptor>
|
|
14
|
+
|
|
15
|
+
@end
|
|
16
|
+
|
|
17
|
+
@implementation DevMenuDetoxTestInterceptor
|
|
18
|
+
|
|
19
|
+
- (BOOL)isOnboardingFinishedKey
|
|
20
|
+
{
|
|
21
|
+
return YES;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
- (BOOL)shouldShowAtLaunch
|
|
25
|
+
{
|
|
26
|
+
return NO;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@end
|
|
30
|
+
|
|
31
|
+
@interface AppDelegate () <RCTBridgeDelegate>
|
|
32
|
+
|
|
33
|
+
@property (nonatomic, strong) NSDictionary *launchOptions;
|
|
34
|
+
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
@implementation AppDelegate
|
|
38
|
+
|
|
39
|
+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
|
40
|
+
{
|
|
41
|
+
[DevMenuTestInterceptorManager setTestInterceptor:[DevMenuDetoxTestInterceptor new]];
|
|
42
|
+
|
|
43
|
+
self.launchOptions = launchOptions;
|
|
44
|
+
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
|
45
|
+
|
|
46
|
+
EXDevLauncherController *controller = [EXDevLauncherController sharedInstance];
|
|
47
|
+
[controller startWithWindow:self.window delegate:(id<EXDevLauncherControllerDelegate>)self launchOptions:launchOptions];
|
|
48
|
+
|
|
49
|
+
[super application:application didFinishLaunchingWithOptions:launchOptions];
|
|
50
|
+
|
|
51
|
+
return YES;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
- (RCTBridge *)initializeReactNativeApp
|
|
55
|
+
{
|
|
56
|
+
NSDictionary *launchOptions = [EXDevLauncherController.sharedInstance getLaunchOptions];
|
|
57
|
+
|
|
58
|
+
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
|
|
59
|
+
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
|
|
60
|
+
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
|
|
61
|
+
|
|
62
|
+
UIViewController *rootViewController = [UIViewController new];
|
|
63
|
+
rootViewController.view = rootView;
|
|
64
|
+
self.window.rootViewController = rootViewController;
|
|
65
|
+
[self.window makeKeyAndVisible];
|
|
66
|
+
|
|
67
|
+
return bridge;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
|
|
71
|
+
return [[EXDevLauncherController sharedInstance] sourceUrl];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Linking API
|
|
75
|
+
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
|
76
|
+
if ([EXDevLauncherController.sharedInstance onDeepLink:url options:options]) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return [RCTLinkingManager application:application openURL:url options:options];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Universal Links
|
|
84
|
+
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
|
|
85
|
+
return [RCTLinkingManager application:application
|
|
86
|
+
continueUserActivity:userActivity
|
|
87
|
+
restorationHandler:restorationHandler];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@end
|
|
91
|
+
|
|
92
|
+
@implementation AppDelegate (EXDevLauncherControllerDelegate)
|
|
93
|
+
|
|
94
|
+
- (void)devLauncherController:(EXDevLauncherController *)developmentClientController
|
|
95
|
+
didStartWithSuccess:(BOOL)success
|
|
96
|
+
{
|
|
97
|
+
developmentClientController.appBridge = [self initializeReactNativeApp];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-dev-client",
|
|
3
|
-
"version": "6.0.17
|
|
3
|
+
"version": "6.0.17",
|
|
4
4
|
"description": "Expo Development Client",
|
|
5
5
|
"main": "build/DevClient.js",
|
|
6
6
|
"types": "build/DevClient.d.ts",
|
|
@@ -32,20 +32,21 @@
|
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"homepage": "https://docs.expo.dev/versions/latest/sdk/dev-client/",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"expo-dev-launcher": "6.
|
|
36
|
-
"expo-dev-menu": "7.
|
|
37
|
-
"expo-dev-menu-interface": "2.0.
|
|
38
|
-
"expo-manifests": "1.0.
|
|
39
|
-
"expo-updates-interface": "2.0.
|
|
35
|
+
"expo-dev-launcher": "6.0.17",
|
|
36
|
+
"expo-dev-menu": "7.0.16",
|
|
37
|
+
"expo-dev-menu-interface": "2.0.0",
|
|
38
|
+
"expo-manifests": "~1.0.8",
|
|
39
|
+
"expo-updates-interface": "~2.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"expo-module-scripts": "5.0.
|
|
43
|
-
"expo-test-runner": "0.3.
|
|
42
|
+
"expo-module-scripts": "^5.0.7",
|
|
43
|
+
"expo-test-runner": "0.3.7"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"expo": "
|
|
46
|
+
"expo": "*"
|
|
47
47
|
},
|
|
48
48
|
"jest": {
|
|
49
49
|
"preset": "expo-module-scripts"
|
|
50
|
-
}
|
|
50
|
+
},
|
|
51
|
+
"gitHead": "c78d97bb440547ad05de38f37398e79a7118c52a"
|
|
51
52
|
}
|