Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### Fixes

- Fix stale `turbo_module.*` tags on Android native crashes ([#6823](https://github.com/getsentry/sentry-react-native/pull/6823))
- Forward image attachments to Spotlight instead of stripping them from the envelope ([#6818](https://github.com/getsentry/sentry-react-native/pull/6818))
- Don't crash the iOS app when Sentry is initialized with an invalid DSN ([#6825](https://github.com/getsentry/sentry-react-native/pull/6825))
- Populate the Hermes runtime version on JS profiles instead of sending an empty value ([#6817](https://github.com/getsentry/sentry-react-native/pull/6817))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,10 @@ private void initFragmentInitialFrameTracking() {
final RNSentryReactFragmentLifecycleTracer fragmentLifecycleTracer =
new RNSentryReactFragmentLifecycleTracer(buildInfo, emitNewFrameEvent, logger);

final @Nullable FragmentActivity fragmentActivity = (FragmentActivity) getCurrentActivity();
if (fragmentActivity != null) {
final @Nullable Activity currentActivity = getCurrentActivity();
if (currentActivity instanceof FragmentActivity) {
final @Nullable FragmentManager supportFragmentManager =
fragmentActivity.getSupportFragmentManager();
((FragmentActivity) currentActivity).getSupportFragmentManager();
if (supportFragmentManager != null) {
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleTracer, true);
}
Expand All @@ -188,6 +188,7 @@ private void initFragmentInitialFrameTracking() {

public void initNativeReactNavigationNewFrameTracking(Promise promise) {
this.initFragmentInitialFrameTracking();
promise.resolve(null);
Comment thread
antonis marked this conversation as resolved.
}

Comment thread
sentry[bot] marked this conversation as resolved.
public void initNativeSdk(final ReadableMap rnOptions, Promise promise) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.sentry.react;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import org.junit.Before;
import org.junit.Test;

/**
* {@code initNativeReactNavigationNewFrameTracking} must settle its promise so the JS TurboModule
* tracker pops the startup frame instead of leaking it for the process lifetime.
*/
public class RNSentryInitFrameTrackingTest {

private RNSentryModuleImpl module;
private Promise promise;
private ReactApplicationContext reactContext;

@Before
public void setUp() throws Exception {
reactContext = mock(ReactApplicationContext.class);
PackageManager packageManager = mock(PackageManager.class);
when(packageManager.getPackageInfo(anyString(), anyInt())).thenReturn(new PackageInfo());
when(reactContext.getPackageManager()).thenReturn(packageManager);
when(reactContext.getPackageName()).thenReturn("com.test.app");
module = new RNSentryModuleImpl(reactContext);
promise = mock(Promise.class);
}

@Test
public void resolvesPromiseWhenNoCurrentActivity() {
// No activity -> fragment tracking is skipped, but the promise must still settle.
when(reactContext.getCurrentActivity()).thenReturn(null);

module.initNativeReactNavigationNewFrameTracking(promise);

verify(promise).resolve(isNull());
verify(promise, never()).reject(anyString(), anyString());
}

@Test
public void resolvesPromiseAndStillRegistersFragmentCallbacks() {
// Settling the promise must not skip the frame-tracking registration.
FragmentActivity activity = mock(FragmentActivity.class);
FragmentManager fragmentManager = mock(FragmentManager.class);
when(activity.getSupportFragmentManager()).thenReturn(fragmentManager);
when(reactContext.getCurrentActivity()).thenReturn(activity);

module.initNativeReactNavigationNewFrameTracking(promise);

verify(fragmentManager)
.registerFragmentLifecycleCallbacks(
any(FragmentManager.FragmentLifecycleCallbacks.class), anyBoolean());
verify(promise).resolve(isNull());
verify(promise, never()).reject(anyString(), anyString());
}

@Test
public void resolvesPromiseWhenActivityIsNotFragmentActivity() {
// Brownfield / hybrid host: a plain Activity can't do fragment tracking. The
// cast must not throw (ClassCastException would skip resolve and re-leak the
// tracker frame) — tracking is skipped and the promise still settles.
Activity plainActivity = mock(Activity.class);
when(reactContext.getCurrentActivity()).thenReturn(plainActivity);

module.initNativeReactNavigationNewFrameTracking(promise);

verify(promise).resolve(isNull());
verify(promise, never()).reject(anyString(), anyString());
}
}
Loading