diff --git a/babel.config.js b/babel.config.js index c1d044b7c29..7d688a04fb7 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,6 +3,7 @@ module.exports = function (api) { return { presets: ['module:metro-react-native-babel-preset'], plugins: [ + 'react-native-reanimated/plugin', '@babel/plugin-proposal-export-namespace-from', '@babel/plugin-proposal-export-default-from', ], diff --git a/e2e/config.json b/e2e/config.json index 4a18907cd52..6640a550291 100644 --- a/e2e/config.json +++ b/e2e/config.json @@ -1,7 +1,7 @@ { "globalSetup": "./global-setup.js", "globalTeardown": "./global-teardown.js", - "setupTestFrameworkScriptFile" : "./init.js", + "setupFilesAfterEnv" : ["./init.js"], "testEnvironment": "node", "bail": true, "verbose": true, diff --git a/e2e/init.js b/e2e/init.js index 1e69cab6078..5252c564f3a 100644 --- a/e2e/init.js +++ b/e2e/init.js @@ -1,21 +1,13 @@ const detox = require('detox'); const config = require('../package.json').detox; -const exec = require('shell-utils').exec; -const adapter = require('detox/runners/jest/adapter'); require('detox-testing-library-rnn-adapter').extendDetox(); jest.setTimeout(300000); -jasmine.getEnv().addReporter(adapter); beforeAll(async () => { await detox.init(config, { launchApp: false }); }); afterAll(async () => { - await adapter.afterAll(); await detox.cleanup(); }); - -beforeEach(async () => { - await adapter.beforeEach(); -}); diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000000..66fd19e6741 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,40 @@ +module.exports = { + preset: 'react-native', + transformIgnorePatterns: [ + 'node_modules/(?!(@react-native|react-native|react-native-ui-lib|react-native-animatable)/)', + ], + transform: { + '\\.[jt]sx?$': 'babel-jest', + }, + roots: [ + '/lib/src/', + '/playground/src/', + '/integration/', + '/scripts/', + '/e2e/', + ], + setupFilesAfterEnv: ['./jest-setup.js'], + testPathIgnorePatterns: ['/node_modules/'], + moduleNameMapper: { + 'react-native-navigation/Mock': '/lib/src/Mock', + 'react-native-navigation': '/lib/src', + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/playground/img/layouts@2x.png', + }, + collectCoverageFrom: [ + 'lib/src/**/*.ts', + 'lib/src/**/*.tsx', + 'integration/**/*.js', + '!lib/dist/index.js', + '!lib/dist/Navigation.js', + '!lib/dist/adapters/**/*', + '!lib/dist/interfaces/**/*', + '!lib/dist/**/*.test.*', + '!integration/**/*.test.*', + '!integration/*.test.*', + '!e2e/**/*test.js', + ], + resetMocks: true, + resetModules: true, + coverageReporters: ['json', 'lcov', 'text', 'html'], +}; diff --git a/lib/android/app/build.gradle b/lib/android/app/build.gradle index 51148fea824..b4b39f73837 100644 --- a/lib/android/app/build.gradle +++ b/lib/android/app/build.gradle @@ -3,27 +3,29 @@ import org.gradle.api.tasks.testing.logging.TestExceptionFormat apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } -def DEFAULT_COMPILE_SDK_VERSION = 29 -def DEFAULT_MIN_SDK_VERSION = 19 -def DEFAULT_TARGET_SDK_VERSION = 29 -def kotlinVersion = rootProject.ext.get("RNNKotlinVersion") -def kotlinStdlib = safeExtGet('RNNKotlinStdlib', 'kotlin-stdlib-jdk8') -def kotlinCoroutinesCore = safeExtGet('RNNKotlinCoroutinesCore', '1.4.3') +def safeExtGetFallbackLowerBound(prop, fallback) { + Math.max(safeExtGet(prop,fallback),fallback) +} +def DEFAULT_COMPILE_SDK_VERSION = 30 +def DEFAULT_MIN_SDK_VERSION = 21 +def DEFAULT_TARGET_SDK_VERSION = 30 +def DEFAULT_KOTLIN_VERSION = "1.5.31" +def DEFAULT_KOTLIN_STDLIB = 'kotlin-stdlib-jdk8' +def kotlinVersion = safeExtGet("RNNKotlinVersion", DEFAULT_KOTLIN_VERSION) +def kotlinStdlib = safeExtGet('RNNKotlinStdlib',DEFAULT_KOTLIN_STDLIB ) +def kotlinCoroutinesCore = safeExtGet('RNNKotlinCoroutinesCore', '1.5.2') android { - compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) + compileSdkVersion safeExtGetFallbackLowerBound('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) defaultConfig { - minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) - targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) - versionCode 1 - versionName "1.0" + minSdkVersion safeExtGetFallbackLowerBound('minSdkVersion', DEFAULT_MIN_SDK_VERSION) + targetSdkVersion safeExtGetFallbackLowerBound('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) } buildTypes { release { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalFrameLayout.kt b/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalFrameLayout.kt index 07e0690127b..3ee9ca54849 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalFrameLayout.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalFrameLayout.kt @@ -11,7 +11,7 @@ class ModalFrameLayout(context: ReactContext) : FrameLayout(context) { addView(modalContentLayout, MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT) .apply { val translucent = context.currentActivity?.window?.let { - StatusBarUtils.isTranslucent(context.currentActivity?.window) + StatusBarUtils.isTranslucent(it) } ?: false topMargin = if (translucent) 0 else StatusBarUtils.getStatusBarHeight(context) }) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/utils/StatusBarUtils.java b/lib/android/app/src/main/java/com/reactnativenavigation/utils/StatusBarUtils.java deleted file mode 100644 index 1fb0d92488f..00000000000 --- a/lib/android/app/src/main/java/com/reactnativenavigation/utils/StatusBarUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.reactnativenavigation.utils; - -import android.content.Context; -import android.content.res.Resources; -import android.os.Build; -import android.view.Window; -import android.view.WindowManager; - -import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; -import static com.reactnativenavigation.utils.UiUtils.dpToPx; - -public class StatusBarUtils { - private static final int STATUS_BAR_HEIGHT_M = 24; - private static final int STATUS_BAR_HEIGHT_L = 25; - private static int statusBarHeight = -1; - - public static void saveStatusBarHeight(int height) { - statusBarHeight = height; - } - - public static int getStatusBarHeight(Context context) { - if (statusBarHeight > 0) { - return statusBarHeight; - } - final Resources resources = context.getResources(); - final int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); - statusBarHeight = resourceId > 0 ? - resources.getDimensionPixelSize(resourceId) : - dpToPx(context, Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? STATUS_BAR_HEIGHT_M : STATUS_BAR_HEIGHT_L); - return statusBarHeight; - } - - public static int getStatusBarHeightDp(Context context) { - return (int) UiUtils.pxToDp(context, getStatusBarHeight(context)); - } - - public static boolean isTranslucent(Window window) { - WindowManager.LayoutParams lp = window.getAttributes(); - return lp != null && (lp.flags & FLAG_TRANSLUCENT_STATUS) == FLAG_TRANSLUCENT_STATUS; - } -} diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/utils/StatusBarUtils.kt b/lib/android/app/src/main/java/com/reactnativenavigation/utils/StatusBarUtils.kt new file mode 100644 index 00000000000..07603dd4a03 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/utils/StatusBarUtils.kt @@ -0,0 +1,39 @@ +package com.reactnativenavigation.utils + +import android.content.Context +import android.os.Build +import android.view.Window +import android.view.WindowManager + +object StatusBarUtils { + private const val STATUS_BAR_HEIGHT_M = 24 + private const val STATUS_BAR_HEIGHT_L = 25 + private var statusBarHeight = -1 + @JvmStatic + fun saveStatusBarHeight(height: Int) { + statusBarHeight = height + } + @JvmStatic + fun getStatusBarHeight(context: Context): Int { + if (statusBarHeight > 0) { + return statusBarHeight + } + val resources = context.resources + val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") + statusBarHeight = if (resourceId > 0) resources.getDimensionPixelSize(resourceId) else UiUtils.dpToPx( + context, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) STATUS_BAR_HEIGHT_M else STATUS_BAR_HEIGHT_L + ) + return statusBarHeight + } + @JvmStatic + fun getStatusBarHeightDp(context: Context): Int { + return UiUtils.pxToDp(context, getStatusBarHeight(context).toFloat()) + .toInt() + } + @JvmStatic + fun isTranslucent(window: Window): Boolean { + val lp = window.attributes + return lp != null && lp.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS == WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS + } +} \ No newline at end of file diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java index 3117dcc53be..e4e6704c4cc 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/component/ComponentViewController.java @@ -15,6 +15,7 @@ import com.reactnativenavigation.views.component.ComponentLayout; import androidx.annotation.NonNull; +import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; @@ -137,13 +138,13 @@ public void applyBottomInset() { @Override protected WindowInsetsCompat applyWindowInsets(ViewController view, WindowInsetsCompat insets) { - ViewCompat.onApplyWindowInsets(view.getView(), insets.replaceSystemWindowInsets( - insets.getSystemWindowInsetLeft(), - insets.getSystemWindowInsetTop(), + final WindowInsetsCompat.Builder builder = new WindowInsetsCompat.Builder(); + final WindowInsetsCompat finalInsets = builder.setSystemWindowInsets(Insets.of(insets.getSystemWindowInsetLeft(), + Math.max(insets.getSystemWindowInsetTop() - getTopInset(), 0), insets.getSystemWindowInsetRight(), - Math.max(insets.getSystemWindowInsetBottom() - getBottomInset(), 0) - )); - return insets; + Math.max(insets.getSystemWindowInsetBottom() - getBottomInset(), 0))).build(); + ViewCompat.onApplyWindowInsets(view.getView(), finalInsets); + return finalInsets; } @Override diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java index 81382b1a589..3df53a45b45 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenter.java @@ -90,10 +90,12 @@ public void onCancel() { } private void onShowModalEnd(ViewController toAdd, @Nullable ViewController toRemove, CommandListener listener) { - toAdd.onViewDidAppear(); - if (toRemove != null && toAdd.resolveCurrentOptions(defaultOptions).modal.presentationStyle != ModalPresentationStyle.OverCurrentContext) { - toRemove.detachView(); - } + toAdd.addOnAppearedListener(()->{ + toAdd.onViewDidAppear(); + if (toRemove != null && toAdd.resolveCurrentOptions(defaultOptions).modal.presentationStyle != ModalPresentationStyle.OverCurrentContext) { + toRemove.detachView(); + } + }); listener.onSuccess(toAdd.getId()); } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java index d1237c94d04..c2fcafafb80 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java @@ -5,6 +5,7 @@ import android.view.View; import android.view.ViewGroup; +import com.facebook.react.ReactRootView; import com.reactnativenavigation.options.ButtonOptions; import com.reactnativenavigation.options.Options; import com.reactnativenavigation.options.StackAnimationOptions; @@ -175,9 +176,7 @@ public void push(ViewController child, CommandListener listener) { presenter.getAdditionalPushAnimations(this, child, resolvedOptions), () -> onPushAnimationComplete(child, toRemove, listener)); } else { - child.onViewDidAppear(); - getView().removeView(toRemove.getView()); - listener.onSuccess(child.getId()); + onPushAnimationComplete(child, toRemove, listener); } } else { listener.onSuccess(child.getId()); @@ -191,8 +190,10 @@ public void destroy() { } private void onPushAnimationComplete(ViewController toAdd, ViewController toRemove, CommandListener listener) { - toAdd.onViewDidAppear(); - if (!peek().equals(toRemove)) getView().removeView(toRemove.getView()); + toAdd.addOnAppearedListener(() -> { + toAdd.onViewDidAppear(); + if (!peek().equals(toRemove)) getView().removeView(toRemove.getView()); + }); listener.onSuccess(toAdd.getId()); } @@ -421,12 +422,19 @@ private void addInitialChild(StackLayout stackLayout) { if (isEmpty()) return; ViewController childController = peek(); ViewGroup child = childController.getView(); - child.setId(CompatUtils.generateViewId()); + setChildId(child); childController.addOnAppearedListener(this::startChildrenBellowTopChild); stackLayout.addView(child, 0, matchParentWithBehaviour(new StackBehaviour(this))); presenter.applyInitialChildLayoutOptions(resolveCurrentOptions()); } + private void setChildId(ViewGroup child) { + //From RN > 64 we can't set id to child that is ReactRootView + //see:https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java#L676 + if (!(child instanceof ReactRootView)) + child.setId(CompatUtils.generateViewId()); + } + private void startChildrenBellowTopChild() { ArrayList> children = new ArrayList<>(getChildControllers()); for (int i = children.size() - 2; i >= 0; i--) { diff --git a/lib/android/app/src/reactNative51/java/com/reactnativenavigation/react/NavigationReactNativeHost.java b/lib/android/app/src/reactNative51/java/com/reactnativenavigation/react/NavigationReactNativeHost.java index 8ff7f4dc12b..088c16e359a 100644 --- a/lib/android/app/src/reactNative51/java/com/reactnativenavigation/react/NavigationReactNativeHost.java +++ b/lib/android/app/src/reactNative51/java/com/reactnativenavigation/react/NavigationReactNativeHost.java @@ -8,6 +8,7 @@ import com.facebook.react.common.LifecycleState; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.reactnativenavigation.NavigationApplication; +import com.facebook.hermes.reactexecutor.HermesExecutorFactory; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -44,7 +45,7 @@ protected ReactInstanceManager createReactInstanceManager() { .setJSMainModulePath(getJSMainModuleName()) .setUseDeveloperSupport(getUseDeveloperSupport()) .setRedBoxHandler(getRedBoxHandler()) - .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory()) + .setJavaScriptExecutorFactory(new HermesExecutorFactory()) .setUIImplementationProvider(getUIImplementationProvider()) .setInitialLifecycleState(LifecycleState.BEFORE_CREATE) .setDevBundleDownloadListener(getDevBundleDownloadListener()); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java index 6450567de2d..fedd2e2f773 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalPresenterTest.java @@ -50,6 +50,7 @@ public class ModalPresenterTest extends BaseTest { @Override public void beforeEach() { + super.beforeEach(); Activity activity = newActivity(); ChildControllersRegistry childRegistry = new ChildControllersRegistry(); @@ -210,6 +211,7 @@ public void dismissModal_previousViewIsAddedAtIndex0() { uut.setRootLayout(spy); uut.showModal(modal1, root, new CommandListenerAdapter()); + idleMainLooper(); uut.dismissModal(modal1, root, root, new CommandListenerAdapter()); verify(spy).addView(root.getView(), 0); @@ -237,6 +239,7 @@ public void dismissModal_previousModalIsAddedBackToHierarchy() { verify(modal1).onViewWillAppear(); uut.showModal(modal2, modal1, new CommandListenerAdapter()); + idleMainLooper(); assertThat(modal1.getView().getParent()).isNull(); Shadows.shadowOf(Looper.getMainLooper()).idle(); @@ -253,6 +256,7 @@ public void dismissModal_previousControllerIsNotAddedIfDismissedModalIsNotTop() uut.showModal(modal1, root, new CommandListenerAdapter()); uut.showModal(modal2, modal1, new CommandListenerAdapter()); + idleMainLooper(); assertThat(modal1.getView().getParent()).isNull(); assertThat(root.getView().getParent()).isNull(); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java index 733f517d69d..81affe984a6 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/modal/ModalStackTest.java @@ -13,7 +13,7 @@ import com.reactnativenavigation.viewcontrollers.child.ChildControllersRegistry; import com.reactnativenavigation.viewcontrollers.stack.StackController; import com.reactnativenavigation.viewcontrollers.viewcontroller.ViewController; - +import com.reactnativenavigation.options.TransitionAnimationOptions; import org.junit.Test; import org.mockito.Mockito; @@ -54,6 +54,7 @@ public class ModalStackTest extends BaseTest { @Override public void beforeEach() { + super.beforeEach(); activity = newActivity(); childRegistry = new ChildControllersRegistry(); root = new SimpleViewController(activity, childRegistry, "root", new Options()); @@ -81,6 +82,16 @@ public void beforeEach() { .build(); } + @Test + public void showModal_DidAppearEventShouldWaitForReactViewToBeShown(){ + CommandListener listener = spy(new CommandListenerAdapter()); + uut.showModal(modal1, root, listener); + verify(modal1).addOnAppearedListener(any()); + verify(listener).onSuccess(modal1.getId()); + idleMainLooper(); + verify(modal1).onViewDidAppear(); + } + @Test public void modalRefIsSaved() { disableShowModalAnimation(modal1); @@ -94,6 +105,7 @@ public void modalRefIsSaved() { public void showModal() { CommandListener listener = spy(new CommandListenerAdapter()); uut.showModal(modal1, root, listener); + idleMainLooper(); verify(listener).onSuccess(modal1.getId()); verify(modal1).onViewDidAppear(); assertThat(uut.size()).isOne(); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java index 2490271cb42..9f43fe03868 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/navigator/NavigatorTest.java @@ -507,7 +507,7 @@ public void popTo_FromCorrectStackUpToChild() { StackController stack2 = newStack(child2, child3, child4); BottomTabsController bottomTabsController = newTabs(Arrays.asList(stack1, stack2)); uut.setRoot(bottomTabsController, new CommandListenerAdapter(), reactInstanceManager); - + idleMainLooper(); CommandListenerAdapter listener = spy(new CommandListenerAdapter() { @Override public void onSuccess(String childId) { @@ -670,7 +670,7 @@ public void pop_FromCorrectStackByFindingChildId_Promise() { final StackController stack2 = newStack(child2, child3); BottomTabsController bottomTabsController = newTabs(Arrays.asList(stack1, stack2)); uut.setRoot(bottomTabsController, new CommandListenerAdapter(), reactInstanceManager); - + idleMainLooper(); CommandListenerAdapter listener = spy(new CommandListenerAdapter() { @Override public void onSuccess(String childId) { diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.kt b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.kt index 19bc3c6e0e6..cd36ab5f353 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.kt +++ b/lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.kt @@ -197,7 +197,7 @@ class StackControllerTest : BaseTest() { fun setRoot_pushDuringSetRootAnimationShouldNotCrash() { uut.push(child1, CommandListenerAdapter()) uut.push(child2, CommandListenerAdapter()) - + idleMainLooper() uut.setRoot(listOf(child1), CommandListenerAdapter()) uut.push(child3, CommandListenerAdapter()) assertThat(uut.currentChild).isEqualTo(child3) @@ -281,6 +281,7 @@ class StackControllerTest : BaseTest() { disablePushAnimation(child1, child2) uut.push(child1, CommandListenerAdapter()) // Initialize stack with a child uut.push(child2, CommandListenerAdapter()) + idleMainLooper() verify(child2).onViewDidAppear() } @@ -608,8 +609,9 @@ class StackControllerTest : BaseTest() { assertNotChildOf(uut.view, child1.view) uut.push(child1, CommandListenerAdapter()) assertIsChild(uut.view, child1.view) - + idleMainLooper() uut.push(child2, CommandListenerAdapter()) + idleMainLooper() assertIsChild(uut.view, child2) assertNotChildOf(uut.view, child1) } @@ -663,7 +665,7 @@ class StackControllerTest : BaseTest() { val child1View: View = child1.view uut.push(child1, CommandListenerAdapter()) uut.push(child2, CommandListenerAdapter()) - + idleMainLooper() assertIsChild(uut.view, child2View) assertNotChildOf(uut.view, child1View) uut.pop(Options.EMPTY, CommandListenerAdapter()) @@ -715,7 +717,7 @@ class StackControllerTest : BaseTest() { uut.push(child2, CommandListenerAdapter()) uut.push(child3, CommandListenerAdapter()) uut.push(child4, CommandListenerAdapter()) - + idleMainLooper() uut.popTo(child2, Options.EMPTY, CommandListenerAdapter()) verify(animator, never()).pop(any(), eq(child1), any(), any(), any()) verify(animator, never()).pop(any(), eq(child2), any(), any(), any()) @@ -729,6 +731,7 @@ class StackControllerTest : BaseTest() { uut.push(child1, mock()) uut.push(child2, mock()) uut.push(child3, mock()) + idleMainLooper() uut.popTo(child1, Options.EMPTY, mock()) animator.endPushAnimation(child3) assertContainsOnlyId(child1.id) @@ -759,8 +762,8 @@ class StackControllerTest : BaseTest() { disablePushAnimation(child1, child2, child3) uut.push(child1, CommandListenerAdapter()) uut.push(child2, CommandListenerAdapter()) - uut.push(child3, CommandListenerAdapter()) + idleMainLooper() uut.popToRoot(Options.EMPTY, object : CommandListenerAdapter() { override fun onSuccess(childId: String) { verify(animator).pop(eq(child1), eq(child3), any(), any(), any()) @@ -776,6 +779,7 @@ class StackControllerTest : BaseTest() { uut.push(child1, CommandListenerAdapter()) uut.push(child2, CommandListenerAdapter()) uut.push(child3, CommandListenerAdapter()) + idleMainLooper() uut.popToRoot(Options.EMPTY, object : CommandListenerAdapter() { override fun onSuccess(childId: String) { verify(child1, never()).destroy() @@ -811,6 +815,7 @@ class StackControllerTest : BaseTest() { uut.push(child1, mock()) uut.push(child2, mock()) uut.push(child3, mock()) + idleMainLooper() uut.popToRoot(Options.EMPTY, mock()) animator.endPushAnimation(child3) assertContainsOnlyId(child1.id) diff --git a/lib/ios/RNNModalManager.m b/lib/ios/RNNModalManager.m index f6b379c2765..cf6d6ae4201 100644 --- a/lib/ios/RNNModalManager.m +++ b/lib/ios/RNNModalManager.m @@ -36,12 +36,14 @@ - (void)connectModalHostViewManager:(RCTModalHostViewManager *)modalHostViewMana modalHostViewManager.presentationBlock = ^(UIViewController *reactViewController, UIViewController *viewController, BOOL animated, dispatch_block_t completionBlock) { - [self showModal:viewController - animated:animated - completion:^(NSString *_Nonnull componentId) { - if (completionBlock) - completionBlock(); - }]; + if (reactViewController.presentedViewController != viewController) { + [self showModal:viewController + animated:animated + completion:^(NSString *_Nonnull componentId) { + if (completionBlock) + completionBlock(); + }]; + } }; modalHostViewManager.dismissalBlock = diff --git a/package.json b/package.json index 9d492b46ddf..e56ce67ec32 100644 --- a/package.json +++ b/package.json @@ -66,10 +66,9 @@ "tslib": "1.9.3" }, "devDependencies": { - "@babel/core": "7.10.3", "@babel/plugin-proposal-export-default-from": "7.10.1", "@babel/plugin-proposal-export-namespace-from": "7.10.1", - "@babel/types": "7.6.x", + "@babel/types": "7.15.6", "@react-native-community/blur": "^3.6.0", "@react-native-community/datetimepicker": "^3.4.7", "@react-native-community/eslint-config": "2.0.0", @@ -79,14 +78,14 @@ "@types/detox": "17.14.3", "@types/hoist-non-react-statics": "^3.0.1", "@types/jasmine": "3.5.10", - "@types/jest": "26.0.3", + "@types/jest": "27.0.2", "@types/lodash": "^4.14.149", "@types/react": "16.9.41", "@types/react-native": "0.63.1", "@types/react-test-renderer": "16.9.2", "@typescript-eslint/eslint-plugin": "3.3.0", "@typescript-eslint/parser": "3.3.0", - "babel-jest": "26.1.0", + "babel-jest": "^27.2.5", "clang-format": "^1.4.0", "detox": "18.23.1", "eslint": "7.3.0", @@ -95,16 +94,18 @@ "github-release-notes": "https://github.com/yogevbd/github-release-notes/tarball/e601b3dba72dcd6cba323c1286ea6dd0c0110b58", "husky": "4.2.5", "identity-obj-proxy": "3.0.0", - "jest": "26.1.0", - "jest-circus": "26.1.0", + "@babel/core": "7.15.8", + "@babel/runtime": "7.15.4", + "jest": "^27.2.5", + "jest-circus": "^27.2.5", "lint-staged": "10.2.11", - "metro-react-native-babel-preset": "0.59.0", + "metro-react-native-babel-preset": "0.66.2", "prettier": "2.1.2", - "react": "16.13.1", - "react-native": "0.63.2", + "react": "17.0.2", + "react-native": "0.66.2", "react-native-fast-image": "^8.3.4", "react-native-gesture-handler": "^1.6.1", - "react-native-reanimated": "^1.9.0", + "react-native-reanimated": "2.3.0-beta.2", "react-native-ui-lib": "5.11.0", "react-redux": "5.x.x", "react-test-renderer": "16.13.1", @@ -117,48 +118,6 @@ "detox-testing-library-rnn-adapter": "2.x.x", "remx": "3.x.x" }, - "jest": { - "preset": "react-native", - "transform": { - "^.+\\.js$": "/node_modules/react-native/jest/preprocessor.js" - }, - "roots": [ - "/lib/src/", - "/playground/src/", - "/integration/", - "/scripts/", - "/e2e/" - ], - "setupFilesAfterEnv": [ - "./jest-setup.js" - ], - "moduleNameMapper": { - "react-native-navigation/Mock": "/lib/src/Mock", - "react-native-navigation": "/lib/src", - "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/playground/img/layouts@2x.png" - }, - "collectCoverageFrom": [ - "lib/src/**/*.ts", - "lib/src/**/*.tsx", - "integration/**/*.js", - "!lib/dist/index.js", - "!lib/dist/Navigation.js", - "!lib/dist/adapters/**/*", - "!lib/dist/interfaces/**/*", - "!lib/dist/**/*.test.*", - "!integration/**/*.test.*", - "!integration/*.test.*", - "!e2e/**/*test.js" - ], - "resetMocks": true, - "resetModules": true, - "coverageReporters": [ - "json", - "lcov", - "text", - "html" - ] - }, "husky": { "hooks": { "pre-commit": "lint-staged" diff --git a/playground/android/app/build.gradle b/playground/android/app/build.gradle index 7581e6895af..c156cec030c 100644 --- a/playground/android/app/build.gradle +++ b/playground/android/app/build.gradle @@ -1,24 +1,22 @@ apply plugin: "com.android.application" -def enableHermes = false project.ext.react = [ root : "../../../", entryFile: "index.js", + cliPath : "node_modules/react-native/cli.js", bundleAssetName: "index.android.bundle", bundleInAlpha: true, bundleInBeta: true, - enableHermes: enableHermes, + enableHermes: true, hermesFlagsDebug:['-Xes6-proxy','-output-source-map'], hermesFlagsRelease:['-output-source-map'], hermesCommand: "../../../node_modules/hermes-engine/%OS-BIN%/hermesc", ] -def jscFlavor = 'org.webkit:android-jsc:+' - apply from: "../../../node_modules/react-native/react.gradle" android { - compileSdkVersion 29 + compileSdkVersion rootProject.ext.get("compileSdkVersion") ndkVersion "20.1.5948944" compileOptions { @@ -29,11 +27,11 @@ android { defaultConfig { applicationId "com.reactnativenavigation.playground" minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion rootProject.ext.get("targetSdkVersion") versionCode 1 versionName "1.0" ndk { - abiFilters "armeabi-v7a", "x86", 'x86_64' + abiFilters "armeabi-v7a", "x86", "arm64-v8a", 'x86_64' } testBuildType System.getProperty('testBuildType', 'debug') @@ -63,13 +61,10 @@ dependencies { implementation 'com.facebook.react:react-native:+' implementation project(':react-native-fast-image') - if (enableHermes) { - def hermesPath = "../../node_modules/hermes-engine/android/"; - debugImplementation files(hermesPath + "hermes-debug.aar") - releaseImplementation files(hermesPath + "hermes-release.aar") - } else { - implementation jscFlavor - } + def hermesPath = "../../../node_modules/hermes-engine/android/"; + debugImplementation files(hermesPath + "hermes-debug.aar") + releaseImplementation files(hermesPath + "hermes-release.aar") + //noinspection GradleDynamicVersion implementation project(':react-native-navigation') @@ -80,7 +75,7 @@ dependencies { task copyDownloadableDepsToLibs(type: Copy) { - from configurations.compile + from configurations.implementation into 'libs' } diff --git a/playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.java b/playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.java index bad653f3f50..e0046495a10 100644 --- a/playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.java +++ b/playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.java @@ -6,7 +6,8 @@ import com.reactnativenavigation.NavigationApplication; import com.reactnativenavigation.react.NavigationPackage; import com.reactnativenavigation.react.NavigationReactNativeHost; - +import com.swmansion.reanimated.ReanimatedJSIModulePackage; +import com.facebook.react.bridge.JSIModulePackage; import java.util.ArrayList; import java.util.List; @@ -14,6 +15,10 @@ public class MainApplication extends NavigationApplication { private final ReactNativeHost mReactNativeHost = new NavigationReactNativeHost(this) { + @Override + protected JSIModulePackage getJSIModulePackage() { + return new ReanimatedJSIModulePackage(); + } @Override protected String getJSMainModuleName() { return "index"; diff --git a/playground/android/build.gradle b/playground/android/build.gradle index edd3c51ce9d..79f2f68dfbd 100644 --- a/playground/android/build.gradle +++ b/playground/android/build.gradle @@ -2,9 +2,13 @@ buildscript { ext { - kotlinVersion = "1.3.72" + kotlinVersion = "1.5.31" RNNKotlinVersion = kotlinVersion detoxKotlinVersion = kotlinVersion + compileSdkVersion = 30 + buildToolsVersion = "30.0.2" + minSdkVersion = 21 + targetSdkVersion = 30 } repositories { diff --git a/playground/src/screens/ModalScreen.tsx b/playground/src/screens/ModalScreen.tsx index a61b9f7bd63..e7e6c468bff 100644 --- a/playground/src/screens/ModalScreen.tsx +++ b/playground/src/screens/ModalScreen.tsx @@ -14,6 +14,7 @@ import flags from '../flags'; import testIDs from '../testIDs'; import { Dimensions, Modal, Image, Platform, StyleSheet } from 'react-native'; import { View } from 'react-native-ui-lib'; +import { SafeAreaView } from 'react-native'; const height = Math.round(Dimensions.get('window').height); const MODAL_ANIMATION_DURATION = 350; @@ -163,25 +164,27 @@ export default class ModalScreen extends NavigationComponent { visible={this.state.modalVisible} onRequestClose={() => this.setState({ modalVisible: false })} > -