From ee8070db9104363181e688c68969705c61ef0dec Mon Sep 17 00:00:00 2001 From: Richard Z Date: Thu, 6 Feb 2025 18:00:48 -0500 Subject: [PATCH 1/2] Modifier.sentryTag uses Modifier.Node (#4029) * Modifier.sentryTag uses Modifier.Node * Update Changelog * Add UI test for SentryModifier * Make sentrymodifier a robolectric test * Remove redundant dep --------- Co-authored-by: Markus Hintersteiner Co-authored-by: Roman Zavarnitsyn --- CHANGELOG.md | 2 + buildSrc/src/main/java/Config.kt | 1 + sentry-compose/build.gradle.kts | 5 ++ .../io/sentry/compose/SentryModifier.kt | 41 +++++++++++-- .../compose/SentryModifierComposeTest.kt | 59 +++++++++++++++++++ 5 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 sentry-compose/src/androidUnitTest/kotlin/io/sentry/compose/SentryModifierComposeTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 326043e723a..9b6e1d8b5a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ - Fix `IllegalStateException` when registering `onDrawListener` - Fix SIGABRT native crashes on Motorola devices when encoding a video - Mention javadoc and sources for published artifacts in Gradle `.module` metadata ([#3936](https://github.com/getsentry/sentry-java/pull/3936)) +- (Jetpack Compose) Modifier.sentryTag now uses Modifier.Node ([#4029](https://github.com/getsentry/sentry-java/pull/4029)) + - This allows Composables that use this modifier to be skippable ### Dependencies diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index f508b21b1d2..27892fa8905 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -208,6 +208,7 @@ object Config { val javaFaker = "com.github.javafaker:javafaker:1.0.2" val msgpack = "org.msgpack:msgpack-core:0.9.8" val leakCanaryInstrumentation = "com.squareup.leakcanary:leakcanary-android-instrumentation:2.14" + val composeUiTestJunit4 = "androidx.compose.ui:ui-test-junit4:$composeVersion" } object QualityPlugins { diff --git a/sentry-compose/build.gradle.kts b/sentry-compose/build.gradle.kts index 597a6191543..e782807a266 100644 --- a/sentry-compose/build.gradle.kts +++ b/sentry-compose/build.gradle.kts @@ -60,6 +60,11 @@ kotlin { implementation(Config.TestLibs.mockitoKotlin) implementation(Config.TestLibs.mockitoInline) implementation(Config.Libs.composeNavigation) + implementation(Config.TestLibs.robolectric) + implementation(Config.TestLibs.androidxRunner) + implementation(Config.TestLibs.androidxJunit) + implementation(Config.TestLibs.androidxTestRules) + implementation(Config.TestLibs.composeUiTestJunit4) } } } diff --git a/sentry-compose/src/androidMain/kotlin/io/sentry/compose/SentryModifier.kt b/sentry-compose/src/androidMain/kotlin/io/sentry/compose/SentryModifier.kt index f1f43c9c8bb..39ac3216610 100644 --- a/sentry-compose/src/androidMain/kotlin/io/sentry/compose/SentryModifier.kt +++ b/sentry-compose/src/androidMain/kotlin/io/sentry/compose/SentryModifier.kt @@ -1,8 +1,13 @@ package io.sentry.compose import androidx.compose.ui.Modifier +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.SemanticsModifierNode +import androidx.compose.ui.platform.InspectorInfo +import androidx.compose.ui.semantics.SemanticsConfiguration +import androidx.compose.ui.semantics.SemanticsModifier import androidx.compose.ui.semantics.SemanticsPropertyKey -import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.SemanticsPropertyReceiver public object SentryModifier { @@ -19,11 +24,35 @@ public object SentryModifier { ) @JvmStatic - public fun Modifier.sentryTag(tag: String): Modifier { - return semantics( - properties = { - this[SentryTag] = tag + public fun Modifier.sentryTag(tag: String): Modifier = + this then SentryTagModifierNodeElement(tag) + + private data class SentryTagModifierNodeElement(val tag: String) : + ModifierNodeElement(), SemanticsModifier { + + override val semanticsConfiguration: SemanticsConfiguration = + SemanticsConfiguration().also { + it[SentryTag] = tag } - ) + + override fun create(): SentryTagModifierNode = SentryTagModifierNode(tag) + + override fun update(node: SentryTagModifierNode) { + node.tag = tag + } + + override fun InspectorInfo.inspectableProperties() { + name = "sentryTag" + properties["tag"] = tag + } + } + + private class SentryTagModifierNode(var tag: String) : + Modifier.Node(), + SemanticsModifierNode { + + override fun SemanticsPropertyReceiver.applySemantics() { + this[SentryTag] = tag + } } } diff --git a/sentry-compose/src/androidUnitTest/kotlin/io/sentry/compose/SentryModifierComposeTest.kt b/sentry-compose/src/androidUnitTest/kotlin/io/sentry/compose/SentryModifierComposeTest.kt new file mode 100644 index 00000000000..38aa2585d3f --- /dev/null +++ b/sentry-compose/src/androidUnitTest/kotlin/io/sentry/compose/SentryModifierComposeTest.kt @@ -0,0 +1,59 @@ +package io.sentry.compose + +import android.app.Application +import android.content.ComponentName +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.Box +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.sentry.compose.SentryModifier.sentryTag +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestWatcher +import org.junit.runner.Description +import org.junit.runner.RunWith +import org.robolectric.Shadows +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +@Config(sdk = [30]) +class SentryModifierComposeTest { + + companion object { + private const val TAG_VALUE = "ExampleTagValue" + } + + // workaround for robolectric tests with composeRule + // from https://github.com/robolectric/robolectric/pull/4736#issuecomment-1831034882 + @get:Rule(order = 1) + val addActivityToRobolectricRule = object : TestWatcher() { + override fun starting(description: Description?) { + super.starting(description) + val appContext: Application = ApplicationProvider.getApplicationContext() + Shadows.shadowOf(appContext.packageManager).addActivityIfNotPresent( + ComponentName( + appContext.packageName, + ComponentActivity::class.java.name + ) + ) + } + } + + @get:Rule(order = 2) + val rule = createComposeRule() + + @Test + fun sentryModifierAppliesTag() { + rule.setContent { + Box(modifier = Modifier.sentryTag(TAG_VALUE)) + } + rule.onNode( + SemanticsMatcher(TAG_VALUE) { + it.config.find { (key, _) -> key.name == SentryModifier.TAG }?.value == TAG_VALUE + } + ).assertExists() + } +} From 5786ec3c159759ee2600b7bca02ef1bc07522890 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 7 Feb 2025 09:54:27 +0100 Subject: [PATCH 2/2] Fix compose test version --- buildSrc/src/main/java/Config.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 27892fa8905..a8c3f760ea4 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -208,7 +208,7 @@ object Config { val javaFaker = "com.github.javafaker:javafaker:1.0.2" val msgpack = "org.msgpack:msgpack-core:0.9.8" val leakCanaryInstrumentation = "com.squareup.leakcanary:leakcanary-android-instrumentation:2.14" - val composeUiTestJunit4 = "androidx.compose.ui:ui-test-junit4:$composeVersion" + val composeUiTestJunit4 = "androidx.compose.ui:ui-test-junit4:1.6.8" } object QualityPlugins {