From 67331c36368204edefb8649bfaa8d122780727b6 Mon Sep 17 00:00:00 2001 From: jonathanmos <48201295+jonathanmos@users.noreply.github.com> Date: Wed, 8 Jan 2025 13:18:25 +0200 Subject: [PATCH] RUM-7116: Add radiobutton colors --- .../consumer-rules.pro | 3 + .../semantics/CheckboxSemanticsNodeMapper.kt | 24 +++---- .../RadioButtonSemanticsNodeMapper.kt | 50 ++++++++++--- .../semantics/SwitchSemanticsNodeMapper.kt | 4 +- .../internal/reflection/ComposeReflection.kt | 7 +- .../compose/internal/utils/ReflectionUtils.kt | 4 ++ .../compose/internal/utils/SemanticsUtils.kt | 36 ++++++---- .../CheckboxSemanticsNodeMapperTest.kt | 6 +- .../RadioButtonSemanticsNodeMapperTest.kt | 70 ++++++++++++++----- .../SwitchSemanticsNodeMapperTest.kt | 4 +- .../internal/utils/SemanticsUtilsTest.kt | 17 +++++ 11 files changed, 165 insertions(+), 60 deletions(-) diff --git a/features/dd-sdk-android-session-replay-compose/consumer-rules.pro b/features/dd-sdk-android-session-replay-compose/consumer-rules.pro index d34ebb43c3..9708d1712a 100644 --- a/features/dd-sdk-android-session-replay-compose/consumer-rules.pro +++ b/features/dd-sdk-android-session-replay-compose/consumer-rules.pro @@ -20,6 +20,9 @@ -keep class androidx.compose.material.CheckboxKt$CheckboxImpl$1$1 { ; } +-keep class androidx.compose.material.RadioButtonKt$RadioButton$2$1 { + ; +} -keep class androidx.compose.ui.draw.DrawBehindElement { ; } diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/CheckboxSemanticsNodeMapper.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/CheckboxSemanticsNodeMapper.kt index ed717b9e4b..e32be071db 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/CheckboxSemanticsNodeMapper.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/CheckboxSemanticsNodeMapper.kt @@ -18,6 +18,8 @@ import com.datadog.android.sessionreplay.compose.internal.data.UiContext import com.datadog.android.sessionreplay.compose.internal.utils.ColorUtils import com.datadog.android.sessionreplay.compose.internal.utils.PathUtils import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_BLACK +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_WHITE import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback import com.datadog.android.sessionreplay.utils.ColorStringFormatter @@ -78,6 +80,7 @@ internal class CheckboxSemanticsNodeMapper( ) } + @Suppress("LongMethod") private fun createCheckboxWireframes( parentContext: UiContext, asyncJobStatusCallback: AsyncJobStatusCallback, @@ -89,8 +92,11 @@ internal class CheckboxSemanticsNodeMapper( val rawFillColor = semanticsUtils.resolveCheckboxFillColor(semanticsNode) val rawCheckmarkColor = semanticsUtils.resolveCheckmarkColor(semanticsNode) val fillColorRgba = rawFillColor?.let { convertColor(it) } ?: DEFAULT_COLOR_WHITE + val fallbackColor = parentContext.parentContentColor?.takeIf { colorUtils.isDarkColor(it) }?.let { + DEFAULT_COLOR_WHITE + } ?: DEFAULT_COLOR_BLACK val checkmarkColorRgba = rawCheckmarkColor?.let { convertColor(it) } - ?: getFallbackCheckmarkColor(DEFAULT_COLOR_WHITE) + ?: fallbackColor val parsedFillColor = colorUtils.parseColorSafe(fillColorRgba) val isChecked = isCheckboxChecked(semanticsNode) val checkmarkColor = resolveCheckmarkColor(isChecked, checkmarkColorRgba, parsedFillColor) @@ -141,6 +147,7 @@ internal class CheckboxSemanticsNodeMapper( // if we failed to create a wireframe from the path return createManualCheckedWireframes( + parentContext = parentContext, semanticsNode = semanticsNode, globalBounds = globalBounds, backgroundColor = fillColorRgba, @@ -163,12 +170,15 @@ internal class CheckboxSemanticsNodeMapper( } private fun createManualCheckedWireframes( + parentContext: UiContext, semanticsNode: SemanticsNode, globalBounds: GlobalBounds, backgroundColor: String, borderColor: String ): List { - val strokeColor = getFallbackCheckmarkColor(backgroundColor) + val strokeColor = parentContext.parentContentColor?.takeIf { colorUtils.isDarkColor(it) }?.let { + DEFAULT_COLOR_WHITE + } ?: DEFAULT_COLOR_BLACK val wireframes = mutableListOf() @@ -233,17 +243,7 @@ internal class CheckboxSemanticsNodeMapper( private fun isCheckboxChecked(semanticsNode: SemanticsNode): Boolean = semanticsNode.config.getOrNull(SemanticsProperties.ToggleableState) == ToggleableState.On - private fun getFallbackCheckmarkColor(backgroundColor: String?) = - if (backgroundColor == DEFAULT_COLOR_WHITE) { - DEFAULT_COLOR_BLACK - } else { - DEFAULT_COLOR_WHITE - } - internal companion object { - internal const val DEFAULT_COLOR_BLACK = "#000000FF" - internal const val DEFAULT_COLOR_WHITE = "#FFFFFFFF" - // when we create the checkmark manually, what % of the checkbox size should it be internal const val CHECKMARK_SIZE_FACTOR = 0.5 diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/RadioButtonSemanticsNodeMapper.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/RadioButtonSemanticsNodeMapper.kt index f8b2fa7d40..9b05e1d2e6 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/RadioButtonSemanticsNodeMapper.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/RadioButtonSemanticsNodeMapper.kt @@ -6,20 +6,24 @@ package com.datadog.android.sessionreplay.compose.internal.mappers.semantics +import androidx.annotation.VisibleForTesting import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.semantics.getOrNull +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.compose.internal.data.SemanticsWireframe import com.datadog.android.sessionreplay.compose.internal.data.UiContext import com.datadog.android.sessionreplay.compose.internal.utils.ColorUtils import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_BLACK +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_WHITE import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback import com.datadog.android.sessionreplay.utils.ColorStringFormatter internal class RadioButtonSemanticsNodeMapper( colorStringFormatter: ColorStringFormatter, - semanticsUtils: SemanticsUtils = SemanticsUtils(), + val semanticsUtils: SemanticsUtils = SemanticsUtils(), private val colorUtils: ColorUtils = ColorUtils() ) : AbstractSemanticsNodeMapper( colorStringFormatter, @@ -30,14 +34,39 @@ internal class RadioButtonSemanticsNodeMapper( parentContext: UiContext, asyncJobStatusCallback: AsyncJobStatusCallback ): SemanticsWireframe { - val color = parentContext.parentContentColor?.takeIf { colorUtils.isDarkColor(it) }?.let { + val wireframes = mutableListOf() + + val fallbackColor = parentContext.parentContentColor?.takeIf { colorUtils.isDarkColor(it) }?.let { DEFAULT_COLOR_WHITE } ?: DEFAULT_COLOR_BLACK - val boxWireframe = resolveBoxWireframe(semanticsNode, 0, color) - val dotWireframe = resolveDotWireframe(semanticsNode, 1, color) + + val radioButtonColor = if (isMasked(parentContext)) { + DEFAULT_COLOR_GRAY + } else { + val rawRadioButtonColor = semanticsUtils.resolveRadioButtonColor(semanticsNode) + rawRadioButtonColor?.let { convertColor(it) } + ?: fallbackColor + } + + resolveBoxWireframe( + semanticsNode = semanticsNode, + color = radioButtonColor, + wireframeIndex = 0 + ) + .let { wireframes.add(it) } + + if (!isMasked(parentContext)) { + resolveDotWireframe( + semanticsNode = semanticsNode, + color = radioButtonColor, + wireframeIndex = 1 + ) + ?.let { wireframes.add(it) } + } + return SemanticsWireframe( uiContext = null, - wireframes = listOfNotNull(boxWireframe, dotWireframe) + wireframes = wireframes ) } @@ -47,6 +76,7 @@ internal class RadioButtonSemanticsNodeMapper( color: String ): MobileSegment.Wireframe { val globalBounds = resolveBounds(semanticsNode) + return MobileSegment.Wireframe.ShapeWireframe( id = resolveId(semanticsNode, wireframeIndex), x = globalBounds.x, @@ -87,10 +117,14 @@ internal class RadioButtonSemanticsNodeMapper( } } - companion object { + private fun isMasked(uiContext: UiContext): Boolean { + return uiContext.textAndInputPrivacy != TextAndInputPrivacy.MASK_SENSITIVE_INPUTS + } + + internal companion object { private const val DOT_PADDING_DP = 4 - private const val DEFAULT_COLOR_BLACK = "#000000FF" - private const val DEFAULT_COLOR_WHITE = "#FFFFFFFF" + + @VisibleForTesting internal const val DEFAULT_COLOR_GRAY = "#8E8F94" private const val BOX_BORDER_WIDTH = 1L } } diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SwitchSemanticsNodeMapper.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SwitchSemanticsNodeMapper.kt index 01a75de17e..29c40f7683 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SwitchSemanticsNodeMapper.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SwitchSemanticsNodeMapper.kt @@ -15,6 +15,8 @@ import com.datadog.android.sessionreplay.compose.internal.data.SemanticsWirefram import com.datadog.android.sessionreplay.compose.internal.data.UiContext import com.datadog.android.sessionreplay.compose.internal.utils.ColorUtils import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_BLACK +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_WHITE import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback import com.datadog.android.sessionreplay.utils.ColorStringFormatter @@ -174,7 +176,5 @@ internal class SwitchSemanticsNodeMapper( const val CORNER_RADIUS_DP = 20 const val THUMB_DIAMETER_DP = 20 const val BORDER_WIDTH_DP = 1L - const val DEFAULT_COLOR_BLACK = "#000000FF" - const val DEFAULT_COLOR_WHITE = "#FFFFFFFF" } } diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt index a412efe2cb..c119cbfb0c 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/reflection/ComposeReflection.kt @@ -40,15 +40,18 @@ internal object ComposeReflection { val ColorField = BackgroundElementClass?.getDeclaredFieldSafe("color") val ShapeField = BackgroundElementClass?.getDeclaredFieldSafe("shape") - val CheckDrawingCacheClass = getClassSafe("androidx.compose.material.CheckDrawingCache") - val CheckboxKtClass = getClassSafe("androidx.compose.material.CheckboxKt\$CheckboxImpl\$1\$1") val DrawBehindElementClass = getClassSafe("androidx.compose.ui.draw.DrawBehindElement") + val CheckboxKtClass = getClassSafe("androidx.compose.material.CheckboxKt\$CheckboxImpl\$1\$1") + val RadioButtonKtClass = getClassSafe("androidx.compose.material.RadioButtonKt\$RadioButton\$2\$1") + val CheckDrawingCacheClass = getClassSafe("androidx.compose.material.CheckDrawingCache") + val BorderColorField = CheckboxKtClass?.getDeclaredFieldSafe("\$borderColor\$delegate") val BoxColorField = CheckboxKtClass?.getDeclaredFieldSafe("\$boxColor\$delegate") val CheckCacheField = CheckboxKtClass?.getDeclaredFieldSafe("\$checkCache") val CheckColorField = CheckboxKtClass?.getDeclaredFieldSafe("\$checkColor\$delegate") val CheckPathField = CheckDrawingCacheClass?.getDeclaredFieldSafe("checkPath") val OnDrawField = DrawBehindElementClass?.getDeclaredFieldSafe("onDraw") + val RadioColorField = RadioButtonKtClass?.getDeclaredFieldSafe("\$radioColor") val PaddingElementClass = getClassSafe("androidx.compose.foundation.layout.PaddingElement") val StartField = PaddingElementClass?.getDeclaredFieldSafe("start") diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/ReflectionUtils.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/ReflectionUtils.kt index f61c70e615..257caca1fd 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/ReflectionUtils.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/ReflectionUtils.kt @@ -186,6 +186,10 @@ internal class ReflectionUtils { return ComposeReflection.BoxColorField?.getSafe(onDrawInstance) as? AnimationState<*, *> } + fun getRadioColor(onDrawInstance: Any): AnimationState<*, *>? { + return ComposeReflection.RadioColorField?.getSafe(onDrawInstance) as? AnimationState<*, *> + } + fun getCheckColor(onDrawInstance: Any): AnimationState<*, *>? { return ComposeReflection.CheckColorField?.getSafe(onDrawInstance) as? AnimationState<*, *> } diff --git a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt index 00edf24cdd..1157aef6d1 100644 --- a/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt +++ b/features/dd-sdk-android-session-replay-compose/src/main/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtils.kt @@ -129,21 +129,27 @@ internal class SemanticsUtils(private val reflectionUtils: ReflectionUtils = Ref } internal fun resolveCheckboxFillColor(semanticsNode: SemanticsNode): Long? = - resolveReflectedProperty( + resolveOnDrawProperty( semanticsNode, - CheckmarkFieldType.FILL_COLOR + OnDrawFieldType.FILL_COLOR ) internal fun resolveCheckmarkColor(semanticsNode: SemanticsNode): Long? = - resolveReflectedProperty( + resolveOnDrawProperty( semanticsNode, - CheckmarkFieldType.CHECKMARK_COLOR + OnDrawFieldType.CHECKMARK_COLOR + ) + + internal fun resolveRadioButtonColor(semanticsNode: SemanticsNode): Long? = + resolveOnDrawProperty( + semanticsNode, + OnDrawFieldType.RADIO_BUTTON_COLOR ) internal fun resolveBorderColor(semanticsNode: SemanticsNode): Long? = - resolveReflectedProperty( + resolveOnDrawProperty( semanticsNode, - CheckmarkFieldType.BORDER_COLOR + OnDrawFieldType.BORDER_COLOR ) private fun shrinkInnerBounds( @@ -338,20 +344,23 @@ internal class SemanticsUtils(private val reflectionUtils: ReflectionUtils = Ref } } - private fun resolveReflectedProperty(semanticsNode: SemanticsNode, fieldType: CheckmarkFieldType): Long? { + private fun resolveOnDrawProperty(semanticsNode: SemanticsNode, fieldType: OnDrawFieldType): Long? { val onDrawInstance = resolveOnDrawInstance(semanticsNode) val color = onDrawInstance?.let { when (fieldType) { - CheckmarkFieldType.FILL_COLOR -> { + OnDrawFieldType.FILL_COLOR -> { reflectionUtils.getBoxColor(onDrawInstance) } - CheckmarkFieldType.CHECKMARK_COLOR -> { + OnDrawFieldType.CHECKMARK_COLOR -> { reflectionUtils.getCheckColor(onDrawInstance) } - CheckmarkFieldType.BORDER_COLOR -> { + OnDrawFieldType.BORDER_COLOR -> { reflectionUtils.getBorderColor(onDrawInstance) } + OnDrawFieldType.RADIO_BUTTON_COLOR -> { + reflectionUtils.getRadioColor(onDrawInstance) + } } } @@ -361,11 +370,14 @@ internal class SemanticsUtils(private val reflectionUtils: ReflectionUtils = Ref } internal companion object { - internal enum class CheckmarkFieldType { + internal enum class OnDrawFieldType { FILL_COLOR, CHECKMARK_COLOR, - BORDER_COLOR + BORDER_COLOR, + RADIO_BUTTON_COLOR } + internal const val DEFAULT_COLOR_BLACK = "#000000FF" + internal const val DEFAULT_COLOR_WHITE = "#FFFFFFFF" } internal fun getProgressBarRangeInfo(semanticsNode: SemanticsNode): ProgressBarRangeInfo? { diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/CheckboxSemanticsNodeMapperTest.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/CheckboxSemanticsNodeMapperTest.kt index 1f0e983b14..ab4e6e9e93 100644 --- a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/CheckboxSemanticsNodeMapperTest.kt +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/CheckboxSemanticsNodeMapperTest.kt @@ -18,11 +18,11 @@ import com.datadog.android.sessionreplay.compose.internal.data.UiContext import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.CheckboxSemanticsNodeMapper.Companion.BOX_BORDER_WIDTH_DP import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.CheckboxSemanticsNodeMapper.Companion.CHECKBOX_CORNER_RADIUS import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.CheckboxSemanticsNodeMapper.Companion.CHECKBOX_SIZE_DP -import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.CheckboxSemanticsNodeMapper.Companion.DEFAULT_COLOR_BLACK -import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.CheckboxSemanticsNodeMapper.Companion.DEFAULT_COLOR_WHITE import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.CheckboxSemanticsNodeMapper.Companion.STROKE_WIDTH_DP import com.datadog.android.sessionreplay.compose.internal.utils.ColorUtils import com.datadog.android.sessionreplay.compose.internal.utils.PathUtils +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_BLACK +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_WHITE import com.datadog.android.sessionreplay.compose.test.elmyr.SessionReplayComposeForgeConfigurator import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback @@ -269,7 +269,7 @@ internal class CheckboxSemanticsNodeMapperTest : AbstractSemanticsNodeMapperTest // Then val foregroundWireframe = semanticsWireframe.wireframes[1] as MobileSegment.Wireframe.ShapeWireframe val expectedShapeStyle = MobileSegment.ShapeStyle( - backgroundColor = DEFAULT_COLOR_WHITE, + backgroundColor = DEFAULT_COLOR_BLACK, opacity = 1f, cornerRadius = CHECKBOX_CORNER_RADIUS ) diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/RadioButtonSemanticsNodeMapperTest.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/RadioButtonSemanticsNodeMapperTest.kt index 85e7f5d55b..d79afed72b 100644 --- a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/RadioButtonSemanticsNodeMapperTest.kt +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/RadioButtonSemanticsNodeMapperTest.kt @@ -10,12 +10,13 @@ import androidx.compose.ui.semantics.SemanticsConfiguration import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.semantics.getOrNull +import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.compose.internal.data.UiContext +import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.RadioButtonSemanticsNodeMapper.Companion.DEFAULT_COLOR_GRAY import com.datadog.android.sessionreplay.compose.test.elmyr.SessionReplayComposeForgeConfigurator import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback import fr.xgouchet.elmyr.Forge -import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.LongForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration @@ -41,6 +42,7 @@ import org.mockito.quality.Strictness internal class RadioButtonSemanticsNodeMapperTest : AbstractSemanticsNodeMapperTest() { private lateinit var testedRadioButtonSemanticsNodeMapper: RadioButtonSemanticsNodeMapper + private lateinit var mockSemanticsNode: SemanticsNode @Mock private lateinit var mockSemanticsConfig: SemanticsConfiguration @@ -54,13 +56,23 @@ internal class RadioButtonSemanticsNodeMapperTest : AbstractSemanticsNodeMapperT @StringForgery(regex = "#[0-9A-F]{8}") lateinit var fakeBackgroundColorHexString: String - @Forgery - lateinit var fakeUiContext: UiContext + @Mock + lateinit var mockUiContext: UiContext @BeforeEach override fun `set up`(forge: Forge) { super.`set up`(forge) + mockSemanticsNode = mockSemanticsNode() + + whenever(mockUiContext.textAndInputPrivacy) doReturn TextAndInputPrivacy.MASK_SENSITIVE_INPUTS mockColorStringFormatter(fakeBackgroundColor, fakeBackgroundColorHexString) + whenever(mockSemanticsUtils.resolveRadioButtonColor(mockSemanticsNode)) + .doReturn(fakeBackgroundColor) + + whenever(mockSemanticsUtils.resolveInnerBounds(mockSemanticsNode)) doReturn rectToBounds( + fakeBounds, + fakeDensity + ) testedRadioButtonSemanticsNodeMapper = RadioButtonSemanticsNodeMapper( colorStringFormatter = mockColorStringFormatter, @@ -71,17 +83,13 @@ internal class RadioButtonSemanticsNodeMapperTest : AbstractSemanticsNodeMapperT @Test fun `M return the box wireframe W map {selected = false}`() { // Given - val mockSemanticsNode = mockSemanticsNode() whenever(mockSemanticsNode.config) doReturn mockSemanticsConfig whenever(mockSemanticsConfig.getOrNull(SemanticsProperties.Selected)) doReturn false - whenever(mockSemanticsUtils.resolveInnerBounds(mockSemanticsNode)) doReturn rectToBounds( - fakeBounds, - fakeDensity - ) + // When val actual = testedRadioButtonSemanticsNodeMapper.map( mockSemanticsNode, - fakeUiContext, + mockUiContext, mockAsyncJobStatusCallback ) @@ -96,7 +104,7 @@ internal class RadioButtonSemanticsNodeMapperTest : AbstractSemanticsNodeMapperT cornerRadius = (fakeBounds.size.width / fakeDensity).toLong() / 2 ), border = MobileSegment.ShapeBorder( - color = DEFAULT_COLOR_BLACK, + color = fakeBackgroundColorHexString, width = BOX_BORDER_WIDTH ) ) @@ -106,18 +114,13 @@ internal class RadioButtonSemanticsNodeMapperTest : AbstractSemanticsNodeMapperT @Test fun `M return the box wireframe W map {selected = true}`() { // Given - val mockSemanticsNode = mockSemanticsNode() whenever(mockSemanticsNode.config) doReturn mockSemanticsConfig whenever(mockSemanticsConfig.getOrNull(SemanticsProperties.Selected)) doReturn true - whenever(mockSemanticsUtils.resolveInnerBounds(mockSemanticsNode)) doReturn rectToBounds( - fakeBounds, - fakeDensity - ) // When val actual = testedRadioButtonSemanticsNodeMapper.map( mockSemanticsNode, - fakeUiContext, + mockUiContext, mockAsyncJobStatusCallback ) @@ -132,7 +135,7 @@ internal class RadioButtonSemanticsNodeMapperTest : AbstractSemanticsNodeMapperT cornerRadius = (fakeBounds.size.width / fakeDensity).toLong() / 2 ), border = MobileSegment.ShapeBorder( - color = DEFAULT_COLOR_BLACK, + color = fakeBackgroundColorHexString, width = BOX_BORDER_WIDTH ) ) @@ -144,20 +147,49 @@ internal class RadioButtonSemanticsNodeMapperTest : AbstractSemanticsNodeMapperT width = (fakeBounds.size.width / fakeDensity).toLong() - 2 * DOT_PADDING_DP, height = (fakeBounds.size.height / fakeDensity).toLong() - 2 * DOT_PADDING_DP, shapeStyle = MobileSegment.ShapeStyle( - backgroundColor = DEFAULT_COLOR_BLACK, + backgroundColor = fakeBackgroundColorHexString, cornerRadius = ((fakeBounds.size.width / fakeDensity).toLong() - 2 * DOT_PADDING_DP) / 2 ) ) assertThat(actual.wireframes).containsAll(listOf(boxFrame, dotFrame)) } + @Test + fun `M return the box wireframe W map { masked }`() { + // Given + whenever(mockUiContext.textAndInputPrivacy) doReturn TextAndInputPrivacy.MASK_ALL_INPUTS + + // When + val actual = testedRadioButtonSemanticsNodeMapper.map( + mockSemanticsNode, + mockUiContext, + mockAsyncJobStatusCallback + ) + + // Then + val expected = MobileSegment.Wireframe.ShapeWireframe( + id = (fakeSemanticsId.toLong() shl 32), + x = (fakeBounds.left / fakeDensity).toLong(), + y = (fakeBounds.top / fakeDensity).toLong(), + width = (fakeBounds.size.width / fakeDensity).toLong(), + height = (fakeBounds.size.height / fakeDensity).toLong(), + shapeStyle = MobileSegment.ShapeStyle( + cornerRadius = (fakeBounds.size.width / fakeDensity).toLong() / 2 + ), + border = MobileSegment.ShapeBorder( + color = DEFAULT_COLOR_GRAY, + width = BOX_BORDER_WIDTH + ) + ) + assertThat(actual.wireframes).containsExactly(expected) + } + private fun mockSemanticsNode(): SemanticsNode { return mockSemanticsNodeWithBound {} } companion object { private const val DOT_PADDING_DP = 4 - private const val DEFAULT_COLOR_BLACK = "#000000FF" private const val BOX_BORDER_WIDTH = 1L } } diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SwitchSemanticsNodeMapperTest.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SwitchSemanticsNodeMapperTest.kt index 1a5a74a6a6..a40b36b518 100644 --- a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SwitchSemanticsNodeMapperTest.kt +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/mappers/semantics/SwitchSemanticsNodeMapperTest.kt @@ -15,11 +15,11 @@ import com.datadog.android.sessionreplay.TextAndInputPrivacy import com.datadog.android.sessionreplay.compose.internal.data.UiContext import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.SwitchSemanticsNodeMapper.Companion.BORDER_WIDTH_DP import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.SwitchSemanticsNodeMapper.Companion.CORNER_RADIUS_DP -import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.SwitchSemanticsNodeMapper.Companion.DEFAULT_COLOR_BLACK -import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.SwitchSemanticsNodeMapper.Companion.DEFAULT_COLOR_WHITE import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.SwitchSemanticsNodeMapper.Companion.THUMB_DIAMETER_DP import com.datadog.android.sessionreplay.compose.internal.mappers.semantics.SwitchSemanticsNodeMapper.Companion.TRACK_WIDTH_DP import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_BLACK +import com.datadog.android.sessionreplay.compose.internal.utils.SemanticsUtils.Companion.DEFAULT_COLOR_WHITE import com.datadog.android.sessionreplay.compose.test.elmyr.SessionReplayComposeForgeConfigurator import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.android.sessionreplay.utils.AsyncJobStatusCallback diff --git a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtilsTest.kt b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtilsTest.kt index 92d8eba653..70c59e746c 100644 --- a/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtilsTest.kt +++ b/features/dd-sdk-android-session-replay-compose/src/test/kotlin/com/datadog/android/sessionreplay/compose/internal/utils/SemanticsUtilsTest.kt @@ -188,6 +188,23 @@ class SemanticsUtilsTest { assertThat(result).isEqualTo(fakeColor.value.toLong()) } + @Test + fun `M return radio button fill color W resolveRadioButtonColor`( + @IntForgery fakeColorValue: Int + ) { + // Given + val fakeColor = Color(fakeColorValue) + val mockAnimationState = mock>() + whenever(mockReflectionUtils.getRadioColor(mockOnDraw)) doReturn mockAnimationState + whenever(mockAnimationState.value).thenReturn(fakeColor) + + // When + val result = testedSemanticsUtils.resolveRadioButtonColor(mockSemanticsNode) + + // Then + assertThat(result).isEqualTo(fakeColor.value.toLong()) + } + @Test fun `M return checkmark color W resolveCheckmarkColor`( @IntForgery fakeColorValue: Int