@@ -22,22 +22,36 @@ import androidx.compose.ui.semantics.SemanticsActions
2222import androidx.compose.ui.semantics.SemanticsConfiguration
2323import androidx.compose.ui.semantics.SemanticsModifier
2424import androidx.compose.ui.semantics.getOrNull
25+ import com.datadog.android.Datadog
26+ import com.datadog.android.api.InternalLogger
27+ import com.datadog.android.api.SdkCore
28+ import com.datadog.android.api.feature.FeatureSdkCore
2529import com.datadog.android.compose.DatadogSemanticsPropertyKey
2630import com.datadog.tools.unit.forge.BaseConfigurator
31+ import com.datadog.tools.unit.getFieldValue
32+ import com.datadog.tools.unit.getStaticValue
2733import fr.xgouchet.elmyr.Forge
2834import fr.xgouchet.elmyr.annotation.StringForgery
2935import fr.xgouchet.elmyr.junit5.ForgeConfiguration
3036import fr.xgouchet.elmyr.junit5.ForgeExtension
3137import org.assertj.core.api.Assertions.assertThat
38+ import org.junit.jupiter.api.AfterEach
39+ import org.junit.jupiter.api.BeforeEach
3240import org.junit.jupiter.api.Test
3341import org.junit.jupiter.api.extension.ExtendWith
3442import org.junit.jupiter.api.extension.Extensions
3543import org.junit.jupiter.params.ParameterizedTest
3644import org.junit.jupiter.params.provider.MethodSource
45+ import org.mockito.ArgumentMatchers.isA
46+ import org.mockito.Mock
3747import org.mockito.junit.jupiter.MockitoExtension
3848import org.mockito.junit.jupiter.MockitoSettings
49+ import org.mockito.kotlin.argumentCaptor
3950import org.mockito.kotlin.doReturn
51+ import org.mockito.kotlin.eq
4052import org.mockito.kotlin.mock
53+ import org.mockito.kotlin.same
54+ import org.mockito.kotlin.verify
4155import org.mockito.kotlin.whenever
4256import org.mockito.quality.Strictness
4357import java.util.stream.Stream
@@ -54,6 +68,25 @@ class LayoutNodeUtilsTest {
5468
5569 private val testedLayoutNodeUtils = LayoutNodeUtils ()
5670
71+ @Mock
72+ lateinit var mockSdkCore: FeatureSdkCore
73+
74+ @Mock
75+ lateinit var mockInternalLogger: InternalLogger
76+
77+ @BeforeEach
78+ fun `set up` () {
79+ whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger
80+ val registry: Any = Datadog ::class .java.getStaticValue(" registry" )
81+ val instances: MutableMap <String , SdkCore > = registry.getFieldValue(" instances" )
82+ instances + = DEFAULT_INSTANCE_NAME to mockSdkCore
83+ }
84+
85+ @AfterEach
86+ fun `tear down` () {
87+ Datadog .stopInstance(DEFAULT_INSTANCE_NAME )
88+ }
89+
5790 // region Legacy Compose (SemanticsModifier)
5891
5992 @Test
@@ -131,6 +164,60 @@ class LayoutNodeUtilsTest {
131164
132165 // endregion
133166
167+ // region Error Handling
168+
169+ @Test
170+ fun `M log WARN to telemetry W resolveLayoutNode() {getModifierInfo throws}` () {
171+ // Given
172+ val exception = RuntimeException (" test exception" )
173+ val mockNode = mock<LayoutNode >()
174+ whenever(mockNode.getModifierInfo()).thenThrow(exception)
175+
176+ // When
177+ val result = testedLayoutNodeUtils.resolveLayoutNode(mockNode)
178+
179+ // Then
180+ assertThat(result).isNull()
181+ argumentCaptor< () -> String > {
182+ verify(mockInternalLogger).log(
183+ eq(InternalLogger .Level .WARN ),
184+ eq(listOf (InternalLogger .Target .MAINTAINER , InternalLogger .Target .TELEMETRY )),
185+ capture(),
186+ same(exception),
187+ eq(true ),
188+ eq(null )
189+ )
190+ assertThat(firstValue()).isEqualTo(" LayoutNodeUtils execution failure in resolveLayoutNode." )
191+ }
192+ }
193+
194+ @Test
195+ fun `M log WARN to telemetry W getLayoutNodeBoundsInWindow() {layoutDelegate access throws}` () {
196+ // Given
197+ // mock<LayoutNode>() returns null for unstubbed layoutDelegate, causing NPE when chained
198+ val mockNode = mock<LayoutNode >()
199+
200+ // When
201+ val result = testedLayoutNodeUtils.getLayoutNodeBoundsInWindow(mockNode)
202+
203+ // Then
204+ assertThat(result).isNull()
205+ argumentCaptor< () -> String > {
206+ verify(mockInternalLogger).log(
207+ eq(InternalLogger .Level .WARN ),
208+ eq(listOf (InternalLogger .Target .MAINTAINER , InternalLogger .Target .TELEMETRY )),
209+ capture(),
210+ isA(NullPointerException ::class .java),
211+ eq(true ),
212+ eq(null )
213+ )
214+ assertThat(firstValue())
215+ .isEqualTo(" LayoutNodeUtils execution failure in getLayoutNodeBoundsInWindow." )
216+ }
217+ }
218+
219+ // endregion
220+
134221 // region Private
135222
136223 private fun mockLayoutNodeWithModifiers (
@@ -192,6 +279,13 @@ class LayoutNodeUtilsTest {
192279 }
193280
194281 companion object {
282+
283+ /* *
284+ * LayoutNodeUtils sends telemetry with default SDK core instance, so in the test we must
285+ * register the mocked SDK core with default name from [SdkCoreRegistry.DEFAULT_INSTANCE_NAME].
286+ */
287+ private const val DEFAULT_INSTANCE_NAME = " _dd.sdk_core.default"
288+
195289 @JvmStatic
196290 fun clickableModifierElements (): Stream <ModifierTestCase > = Stream .of(
197291 ModifierTestCase (" TriStateToggleableElement" , TriStateToggleableElement ()),
0 commit comments