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 @@ -15,6 +15,7 @@

### Fixes

- Time to initial display is no longer understated on Android in optimized release builds ([#6692](https://github.com/getsentry/sentry-react-native/pull/6692))
- Fix visionOS compilation ([#6676](https://github.com/getsentry/sentry-react-native/pull/6676))
- A throwing `beforeBreadcrumb` now drops the breadcrumb, and a throwing `tracesSampler` now falls back to the configured `tracesSampleRate` ([#6675](https://github.com/getsentry/sentry-react-native/pull/6675))

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.swmansion.rnscreens.events

import com.facebook.react.uimanager.events.Event

/**
* Stands in for the react-native-screens event, so a test can dispatch an event that carries this
* class name while reporting a different event name. That is what R8 produces when it merges the
* event classes of a release build into one.
*/
class ScreenAppearEvent(
private val name: String,
) : Event<ScreenAppearEvent>() {
override fun getEventName(): String = name
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.uimanager.events.EventDispatcherListener
import com.swmansion.rnscreens.ScreenStackFragment
import com.swmansion.rnscreens.events.ScreenAppearEvent
import io.sentry.ILogger
import io.sentry.android.core.BuildInfoProvider
import io.sentry.android.core.internal.util.FirstDrawDoneListener
import io.sentry.react.RNSentryReactFragmentLifecycleTracer
import org.junit.After
import org.junit.Test
Expand All @@ -18,7 +22,9 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.MockedStatic
import org.mockito.Mockito.mockStatic
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
Expand All @@ -41,6 +47,48 @@ class RNSentryReactFragmentLifecycleTracerTest {
verify(mockEventDispatcher, times(1)).addListener(any())
}

@Test
fun tracerListensForFirstDrawOnScreenAppearEvent() {
val mockEventDispatcher = mock<EventDispatcher>()
mockUIManager(mockEventDispatcher)

mockStatic(FirstDrawDoneListener::class.java).use { firstDrawDoneListener ->
callOnFragmentViewCreated(mock<ScreenStackFragment>(), mockScreenViewWithReactContext())

dispatchEventNamed("topAppear", mockEventDispatcher)

firstDrawDoneListener.verify {
FirstDrawDoneListener.registerForNextDraw(
any(View::class.java),
any(Runnable::class.java),
any(BuildInfoProvider::class.java),
)
}
verify(mockEventDispatcher, times(1)).removeListener(any())
}
}

@Test
fun tracerDoesNotListenForFirstDrawOnOtherEvents() {
val mockEventDispatcher = mock<EventDispatcher>()
mockUIManager(mockEventDispatcher)

mockStatic(FirstDrawDoneListener::class.java).use { firstDrawDoneListener ->
callOnFragmentViewCreated(mock<ScreenStackFragment>(), mockScreenViewWithReactContext())

dispatchEvent(ScreenAppearEvent("topWillAppear"), mockEventDispatcher)

firstDrawDoneListener.verify({
FirstDrawDoneListener.registerForNextDraw(
any(View::class.java),
any(Runnable::class.java),
any(BuildInfoProvider::class.java),
)
}, never())
verify(mockEventDispatcher, never()).removeListener(any())
}
}

@Test
fun tracerRemovesListenerWhenFragmentViewDestroyed() {
val mockEventDispatcher = mock<EventDispatcher>()
Expand Down Expand Up @@ -119,6 +167,25 @@ class RNSentryReactFragmentLifecycleTracerTest {
tracer.onFragmentViewDestroyed(mock(), mockFragment)
}

private fun dispatchEventNamed(
eventName: String,
mockEventDispatcher: EventDispatcher,
) = dispatchEvent(
mock<Event<*>> {
whenever(it.eventName).thenReturn(eventName)
},
mockEventDispatcher,
)

private fun dispatchEvent(
event: Event<*>,
mockEventDispatcher: EventDispatcher,
) {
val listener = argumentCaptor<EventDispatcherListener>()
verify(mockEventDispatcher, times(1)).addListener(listener.capture())
listener.firstValue.onEventDispatch(event)
}

private fun createSutWith(): RNSentryReactFragmentLifecycleTracer {
val logger: ILogger = mock()
val buildInfo = BuildInfoProvider(logger)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
import org.jetbrains.annotations.Nullable;

public class RNSentryReactFragmentLifecycleTracer extends FragmentLifecycleCallbacks {
/**
* The name react-native-screens uses for its screen appear event. Matching on the class name
* instead would break, because R8 can merge several event classes into one.
*/
private static final String SCREEN_APPEAR_EVENT_NAME = "topAppear";
Comment thread
TaduJR marked this conversation as resolved.

private @NotNull final BuildInfoProvider buildInfoProvider;
private @NotNull final Runnable emitNewFrameEvent;
Expand Down Expand Up @@ -90,8 +95,7 @@ public void onFragmentViewCreated(
new EventDispatcherListenerWrapper(eventDispatcher) {
@Override
public void onEventDispatch(Event event) {
if ("com.swmansion.rnscreens.events.ScreenAppearEvent"
.equals(event.getClass().getCanonicalName())) {
if (SCREEN_APPEAR_EVENT_NAME.equals(event.getEventName())) {
this.dispatcher.removeListener(this);
listenerWrapperMap.remove(f);
FirstDrawDoneListener.registerForNextDraw(v, emitNewFrameEvent, buildInfoProvider);
Expand Down
Loading