From 68aa3f295ee7a6e355327ee438fa2cbfe1a1f967 Mon Sep 17 00:00:00 2001 From: Jonathan Moskovich <48201295+jonathanmos@users.noreply.github.com> Date: Tue, 12 Dec 2023 16:00:13 +0200 Subject: [PATCH] Extract textmapper logic --- .../ReactTextPropertiesResolver.kt | 133 ++++++++++++++ .../sessionreplay/ReactTextShadowNodeUtils.kt | 55 ++++-- .../sessionreplay/mappers/ReactTextMapper.kt | 173 ++---------------- .../mappers/ReactTextMapperTest.kt | 119 ++++-------- 4 files changed, 231 insertions(+), 249 deletions(-) create mode 100644 packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt diff --git a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt new file mode 100644 index 000000000..ca68d00c9 --- /dev/null +++ b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt @@ -0,0 +1,133 @@ +package com.datadog.reactnative.sessionreplay + +import android.view.Gravity +import android.widget.TextView +import com.datadog.android.sessionreplay.model.MobileSegment +import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized +import com.datadog.reactnative.sessionreplay.extensions.densityNormalized +import com.facebook.react.views.text.ReactTextShadowNode + +internal class ReactTextPropertiesResolver( + private val reactTextShadowNodeUtils: ReactTextShadowNodeUtils +) { + internal fun resolveShadowProperties( + view: TextView, + pixelDensity: Float, + shapeStyle: MobileSegment.ShapeStyle?, + border: MobileSegment.ShapeBorder?, + textWireframe: MobileSegment.Wireframe.TextWireframe + ): MobileSegment.Wireframe { + + val shadowNode = reactTextShadowNodeUtils.getShadowNode(view.id) as? ReactTextShadowNode + + val textStyle = resolveTextStyle(view, pixelDensity, shadowNode) + val textPosition = resolveTextPosition(view, pixelDensity, shadowNode) + + return textWireframe.copy( + shapeStyle = shapeStyle, + border = border, + textStyle = textStyle, + textPosition = textPosition + ) + } + + private fun resolveTextPosition( + view: TextView, + pixelsDensity: Float, + shadowNode: ReactTextShadowNode? + ): MobileSegment.TextPosition { + return MobileSegment.TextPosition( + resolvePadding(view, pixelsDensity), + resolveAlignment(view, shadowNode) + ) + } + + private fun resolveTextStyle( + view: TextView, + pixelsDensity: Float, + shadowNode: ReactTextShadowNode? + ): MobileSegment.TextStyle { + return MobileSegment.TextStyle( + family = reactTextShadowNodeUtils.getFontFamily(view, shadowNode), + size = reactTextShadowNodeUtils.getFontSize(view, shadowNode).convertToDensityNormalized(pixelsDensity), + color = resolveTextColor(view, shadowNode) + ) + } + + private fun resolvePadding(textView: TextView, pixelsDensity: Float): MobileSegment.Padding { + return MobileSegment.Padding( + top = textView.totalPaddingTop.densityNormalized(pixelsDensity).toLong(), + bottom = textView.totalPaddingBottom.densityNormalized(pixelsDensity).toLong(), + left = textView.totalPaddingStart.densityNormalized(pixelsDensity).toLong(), + right = textView.totalPaddingEnd.densityNormalized(pixelsDensity).toLong() + ) + } + + private fun resolveAlignment( + textView: TextView, + shadowNode: ReactTextShadowNode? + ): MobileSegment.Alignment { + return when (textView.textAlignment) { + TextView.TEXT_ALIGNMENT_GRAVITY -> resolveShadowAlignment(textView, shadowNode = shadowNode) + TextView.TEXT_ALIGNMENT_CENTER -> MobileSegment.Alignment( + horizontal = MobileSegment.Horizontal.CENTER, + vertical = MobileSegment.Vertical.CENTER + ) + + TextView.TEXT_ALIGNMENT_TEXT_END, + TextView.TEXT_ALIGNMENT_VIEW_END -> MobileSegment.Alignment( + horizontal = MobileSegment.Horizontal.RIGHT, + vertical = MobileSegment.Vertical.CENTER + ) + + TextView.TEXT_ALIGNMENT_TEXT_START, + TextView.TEXT_ALIGNMENT_VIEW_START -> MobileSegment.Alignment( + horizontal = MobileSegment.Horizontal.LEFT, + vertical = MobileSegment.Vertical.CENTER + ) + + else -> MobileSegment.Alignment( + horizontal = MobileSegment.Horizontal.LEFT, + vertical = MobileSegment.Vertical.CENTER + ) + } + } + + private fun resolveShadowAlignment( + view: TextView, + shadowNode: ReactTextShadowNode? + ): MobileSegment.Alignment { + + val shadowGravity = reactTextShadowNodeUtils.getGravity(view, shadowNode) + + + val horizontalAlignment = when (shadowGravity.and(Gravity.HORIZONTAL_GRAVITY_MASK)) { + Gravity.START, + Gravity.LEFT -> MobileSegment.Horizontal.LEFT + + Gravity.END, + Gravity.RIGHT -> MobileSegment.Horizontal.RIGHT + + Gravity.CENTER, + Gravity.CENTER_HORIZONTAL -> MobileSegment.Horizontal.CENTER + + else -> MobileSegment.Horizontal.LEFT + } + val verticalAlignment = when (shadowGravity.and(Gravity.VERTICAL_GRAVITY_MASK)) { + Gravity.TOP -> MobileSegment.Vertical.TOP + Gravity.BOTTOM -> MobileSegment.Vertical.BOTTOM + + Gravity.CENTER_VERTICAL, + Gravity.CENTER -> MobileSegment.Vertical.CENTER + + else -> MobileSegment.Vertical.CENTER + } + + return MobileSegment.Alignment(horizontalAlignment, verticalAlignment) + } + + private fun resolveTextColor(view: TextView, shadowNode: ReactTextShadowNode?): String { + return formatAsRgba(reactTextShadowNodeUtils.getColor(view, shadowNode)) + } +} + diff --git a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextShadowNodeUtils.kt b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextShadowNodeUtils.kt index 0276bb39d..c080a0e5d 100644 --- a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextShadowNodeUtils.kt +++ b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextShadowNodeUtils.kt @@ -1,6 +1,10 @@ package com.datadog.reactnative.sessionreplay +import android.annotation.SuppressLint +import android.graphics.Typeface import android.util.Log +import android.widget.TextView +import androidx.annotation.VisibleForTesting import com.facebook.react.bridge.ReactContext import com.facebook.react.uimanager.ReactShadowNode import com.facebook.react.uimanager.UIImplementation @@ -13,34 +17,42 @@ import okhttp3.internal.wait internal class ReactTextShadowNodeUtils( private val reactContext: ReactContext ) { - internal fun getFontSize(shadowNode: ReactTextShadowNode): Int { - shadowNode.javaClass.superclass?.getDeclaredField("mTextAttributes").let { - it?.isAccessible = true - val textAttributes = it?.get(shadowNode) as TextAttributes - return textAttributes.effectiveFontSize + internal fun getFontSize(view: TextView, shadowNode: ReactTextShadowNode?): Long { + shadowNode?.javaClass?.superclass?.getDeclaredField("mTextAttributes")?.let { + it.isAccessible = true + val textAttributes = it.get(shadowNode) as TextAttributes + return textAttributes.effectiveFontSize.toLong() } + + return view.textSize.toLong() } - internal fun getFontFamily(shadowNode: ReactTextShadowNode): String? { - shadowNode.javaClass.superclass.getDeclaredField("mFontFamily").let { + internal fun getFontFamily(view: TextView, shadowNode: ReactTextShadowNode?): String { + shadowNode?.javaClass?.superclass?.getDeclaredField("mFontFamily")?.let { it.isAccessible = true return it.get(shadowNode)?.toString() + ?: resolveFontFamily(view.typeface) } + + return resolveFontFamily(view.typeface) } - internal fun getGravity(viewId: Int): Int? { - val shadowNode = getShadowNode(viewId) as? ReactTextShadowNode ?: return null - shadowNode.javaClass.superclass.getDeclaredField("mTextAlign").let { + internal fun getGravity(view: TextView, shadowNode: ReactTextShadowNode?): Int { + shadowNode?.javaClass?.superclass?.getDeclaredField("mTextAlign")?.let { it.isAccessible = true return it.getInt(shadowNode) } + + return view.gravity } - internal fun getColor(shadowNode: ReactTextShadowNode): Int { - shadowNode.javaClass.superclass.getDeclaredField("mColor").let { + internal fun getColor(view: TextView, shadowNode: ReactTextShadowNode?): Int { + shadowNode?.javaClass?.superclass?.getDeclaredField("mColor")?.let { it.isAccessible = true return it.getInt(shadowNode) } + + return view.currentTextColor } internal fun getShadowNode(viewId: Int): ReactShadowNode>? { @@ -78,4 +90,23 @@ internal class ReactTextShadowNodeUtils( return value.resolveShadowNode(tag) } } + + @SuppressLint("VisibleForTests") + private fun resolveFontFamily(typeface: Typeface?): String = + typefaceMap[typeface] + ?: SANS_SERIF_FAMILY_NAME + + internal companion object { + @VisibleForTesting + internal const val SANS_SERIF_FAMILY_NAME = "roboto, sans-serif" + private const val SERIF_FAMILY_NAME = "serif" + private const val MONOSPACE_FAMILY_NAME = "monospace" + + @VisibleForTesting + internal val typefaceMap: Map = mapOf( + Typeface.SANS_SERIF to SANS_SERIF_FAMILY_NAME, + Typeface.MONOSPACE to MONOSPACE_FAMILY_NAME, + Typeface.SERIF to SERIF_FAMILY_NAME + ) + } } diff --git a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapper.kt b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapper.kt index 17fe87242..b3a6aaf7f 100644 --- a/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapper.kt +++ b/packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapper.kt @@ -6,40 +6,32 @@ package com.datadog.reactnative.sessionreplay.mappers -import android.graphics.Typeface -import android.view.Gravity import android.widget.TextView -import androidx.annotation.VisibleForTesting import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback import com.datadog.android.sessionreplay.internal.recorder.MappingContext import com.datadog.android.sessionreplay.internal.recorder.mapper.TextViewMapper import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.reactnative.sessionreplay.ReactTextShadowNodeUtils -import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized -import com.datadog.reactnative.sessionreplay.extensions.densityNormalized -import com.datadog.reactnative.sessionreplay.formatAsRgba +import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver import com.datadog.reactnative.sessionreplay.extensions.resolveShapeAndBorder import com.datadog.reactnative.sessionreplay.getReactBackgroundFromDrawable import com.facebook.react.bridge.ReactContext -import com.facebook.react.views.text.ReactTextShadowNode import com.facebook.react.views.view.ReactViewBackgroundDrawable internal class ReactTextMapper( private val reactContext: ReactContext, private val reactTextShadowNodeUtils: ReactTextShadowNodeUtils = ReactTextShadowNodeUtils(reactContext), + private val reactTextPropertiesResolver: ReactTextPropertiesResolver + = ReactTextPropertiesResolver(reactTextShadowNodeUtils) ): TextViewMapper() { override fun map( view: TextView, mappingContext: MappingContext, asyncJobStatusCallback: AsyncJobStatusCallback - ): List { + ): List { val wireframes = super.map(view, mappingContext, asyncJobStatusCallback) - val textWireframe = wireframes.find { - it is MobileSegment.Wireframe.TextWireframe - } as? MobileSegment.Wireframe.TextWireframe ?: return emptyList() - val shadowNode = reactTextShadowNodeUtils.getShadowNode(view.id) as? ReactTextShadowNode val pixelDensity = mappingContext.systemInformation.screenDensity // view.alpha is the value of the opacity prop on the js side @@ -53,150 +45,21 @@ internal class ReactTextMapper( ?: view.background?.resolveShapeStyleAndBorder(opacity) ?: (null to null) - val textStyle = resolveTextStyle(view, pixelDensity, shadowNode) - val textPosition = resolveTextPosition(view, pixelDensity) - - return listOf( - textWireframe.copy( - shapeStyle = shapeStyle, - border = border, - textStyle = textStyle, - textPosition = textPosition - ) - ) - } - - private fun resolveTextPosition( - view: TextView, - pixelsDensity: Float - ): MobileSegment.TextPosition { - return MobileSegment.TextPosition( - resolvePadding(view, pixelsDensity), - resolveAlignment(view) - ) - } - - private fun resolveTextStyle( - view: TextView, - pixelsDensity: Float, - shadowNode: ReactTextShadowNode? - ): MobileSegment.TextStyle { - return MobileSegment.TextStyle( - family = getFontFamily(view, shadowNode), - size = resolveFontSize(view, shadowNode).convertToDensityNormalized(pixelsDensity), - color = resolveTextColor(view, shadowNode) - ) - } - - private fun getFontFamily(view: TextView, shadowNode: ReactTextShadowNode?): String { - if (shadowNode != null) { - val shadowFontFamily = - reactTextShadowNodeUtils.getFontFamily(shadowNode) - return shadowFontFamily ?: resolveFontFamily(view.typeface) - } - - return resolveFontFamily(view.typeface) - } - - private fun resolvePadding(textView: TextView, pixelsDensity: Float): MobileSegment.Padding { - return MobileSegment.Padding( - top = textView.totalPaddingTop.densityNormalized(pixelsDensity).toLong(), - bottom = textView.totalPaddingBottom.densityNormalized(pixelsDensity).toLong(), - left = textView.totalPaddingStart.densityNormalized(pixelsDensity).toLong(), - right = textView.totalPaddingEnd.densityNormalized(pixelsDensity).toLong() - ) - } - - private fun resolveAlignment( - textView: TextView, - ): MobileSegment.Alignment { - return when (textView.textAlignment) { - TextView.TEXT_ALIGNMENT_GRAVITY -> resolveAlignmentFromGravity(textView) - TextView.TEXT_ALIGNMENT_CENTER -> MobileSegment.Alignment( - horizontal = MobileSegment.Horizontal.CENTER, - vertical = MobileSegment.Vertical.CENTER - ) - - TextView.TEXT_ALIGNMENT_TEXT_END, - TextView.TEXT_ALIGNMENT_VIEW_END -> MobileSegment.Alignment( - horizontal = MobileSegment.Horizontal.RIGHT, - vertical = MobileSegment.Vertical.CENTER - ) - - TextView.TEXT_ALIGNMENT_TEXT_START, - TextView.TEXT_ALIGNMENT_VIEW_START -> MobileSegment.Alignment( - horizontal = MobileSegment.Horizontal.LEFT, - vertical = MobileSegment.Vertical.CENTER - ) - - else -> MobileSegment.Alignment( - horizontal = MobileSegment.Horizontal.LEFT, - vertical = MobileSegment.Vertical.CENTER - ) + val result: MutableList = mutableListOf() + wireframes.forEach{ + if (it is MobileSegment.Wireframe.TextWireframe) { + result.add(reactTextPropertiesResolver.resolveShadowProperties( + view, + pixelDensity, + shapeStyle, + border, + it + )) + } else { + result.add(it) + } } - } - - private fun resolveAlignmentFromGravity( - view: TextView - ): MobileSegment.Alignment { - val shadowGravity = reactTextShadowNodeUtils.getGravity(view.id) - ?: view.gravity - - val horizontalAlignment = when (shadowGravity.and(Gravity.HORIZONTAL_GRAVITY_MASK)) { - Gravity.START, - Gravity.LEFT -> MobileSegment.Horizontal.LEFT - - Gravity.END, - Gravity.RIGHT -> MobileSegment.Horizontal.RIGHT - - Gravity.CENTER, - Gravity.CENTER_HORIZONTAL -> MobileSegment.Horizontal.CENTER - - else -> MobileSegment.Horizontal.LEFT - } - val verticalAlignment = when (shadowGravity.and(Gravity.VERTICAL_GRAVITY_MASK)) { - Gravity.TOP -> MobileSegment.Vertical.TOP - Gravity.BOTTOM -> MobileSegment.Vertical.BOTTOM - - Gravity.CENTER_VERTICAL, - Gravity.CENTER -> MobileSegment.Vertical.CENTER - - else -> MobileSegment.Vertical.CENTER - } - - return MobileSegment.Alignment(horizontalAlignment, verticalAlignment) - } - - private fun resolveFontSize(view: TextView, shadowNode: ReactTextShadowNode?): Long { - if (shadowNode != null) { - return reactTextShadowNodeUtils.getFontSize(shadowNode).toLong() - } - return view.textSize.toLong() - } - - private fun resolveTextColor(view: TextView, shadowNode: ReactTextShadowNode?): String { - if (shadowNode != null) { - return formatAsRgba(reactTextShadowNodeUtils.getColor(shadowNode)) - } - - return formatAsRgba(view.currentTextColor) - } - - private fun resolveFontFamily(typeface: Typeface?): String = - typefaceMap[typeface] - ?: SANS_SERIF_FAMILY_NAME - - internal companion object { - @VisibleForTesting - internal const val SANS_SERIF_FAMILY_NAME = "roboto, sans-serif" - private const val SERIF_FAMILY_NAME = "serif" - private const val MONOSPACE_FAMILY_NAME = "monospace" - @VisibleForTesting - internal val typefaceMap: Map = mapOf( - Typeface.SANS_SERIF to SANS_SERIF_FAMILY_NAME, - Typeface.MONOSPACE to MONOSPACE_FAMILY_NAME, - Typeface.SERIF to SERIF_FAMILY_NAME - ) + return result } } diff --git a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapperTest.kt b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapperTest.kt index 8abece272..366ff90bb 100644 --- a/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapperTest.kt +++ b/packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapperTest.kt @@ -10,8 +10,7 @@ import com.datadog.android.sessionreplay.internal.recorder.MappingContext import com.datadog.android.sessionreplay.internal.recorder.SystemInformation import com.datadog.android.sessionreplay.model.MobileSegment import com.datadog.reactnative.sessionreplay.ReactTextShadowNodeUtils -import com.datadog.reactnative.sessionreplay.mappers.ReactTextMapper.Companion.SANS_SERIF_FAMILY_NAME -import com.datadog.reactnative.sessionreplay.mappers.ReactTextMapper.Companion.typefaceMap +import com.datadog.reactnative.sessionreplay.ReactTextShadowNodeUtils.Companion.typefaceMap import com.facebook.react.bridge.ReactContext import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.FloatForgery @@ -27,6 +26,7 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.whenever import org.mockito.quality.Strictness @@ -89,6 +89,9 @@ internal class ReactTextMapperTest { whenever(mockSystemInformation.screenDensity).thenReturn(1.0f) whenever(mockMappingContext.systemInformation).thenReturn(mockSystemInformation) whenever(mockTextView.text).thenReturn(forge.aString()) + whenever(mockTextView.typeface).thenReturn(Typeface.SANS_SERIF) + whenever(mockReactTextShadowNodeUtils.getFontFamily(any(), anyOrNull())) + .thenReturn(typefaceMap[Typeface.SANS_SERIF]) expectedWireframe = MobileSegment.Wireframe.TextWireframe( id = fakeId.toLong(), @@ -133,6 +136,7 @@ internal class ReactTextMapperTest { // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -148,6 +152,7 @@ internal class ReactTextMapperTest { // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -163,6 +168,7 @@ internal class ReactTextMapperTest { // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -178,6 +184,7 @@ internal class ReactTextMapperTest { // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -193,6 +200,7 @@ internal class ReactTextMapperTest { // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -203,12 +211,14 @@ internal class ReactTextMapperTest { @Test fun `M return LEFT W map { shadow gravity LEFT }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())).thenReturn(Gravity.LEFT) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) + .thenReturn(Gravity.LEFT) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -218,12 +228,14 @@ internal class ReactTextMapperTest { @Test fun `M return LEFT W map { shadow gravity START }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())).thenReturn(Gravity.START) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) + .thenReturn(Gravity.START) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -233,12 +245,14 @@ internal class ReactTextMapperTest { @Test fun `M return RIGHT W map { shadow gravity RIGHT }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())).thenReturn(Gravity.RIGHT) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) + .thenReturn(Gravity.RIGHT) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -248,12 +262,14 @@ internal class ReactTextMapperTest { @Test fun `M return RIGHT W map { shadow gravity END }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())).thenReturn(Gravity.END) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) + .thenReturn(Gravity.END) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -263,13 +279,14 @@ internal class ReactTextMapperTest { @Test fun `M return CENTER W map { shadow gravity CENTER_HORIZONTAL }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) .thenReturn(Gravity.CENTER_HORIZONTAL) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -279,13 +296,14 @@ internal class ReactTextMapperTest { @Test fun `M return CENTER W map { shadow gravity CENTER_VERTICAL }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) .thenReturn(Gravity.CENTER_VERTICAL) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -295,13 +313,14 @@ internal class ReactTextMapperTest { @Test fun `M return TOP W map { shadow gravity TOP }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) .thenReturn(Gravity.TOP) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -311,13 +330,14 @@ internal class ReactTextMapperTest { @Test fun `M return BOTTOM W map { shadow gravity BOTTOM }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) .thenReturn(Gravity.BOTTOM) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -327,13 +347,14 @@ internal class ReactTextMapperTest { @Test fun `M return CENTER W map { shadow gravity horizontal CENTER }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) .thenReturn(Gravity.CENTER) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -343,13 +364,14 @@ internal class ReactTextMapperTest { @Test fun `M return CENTER W map { shadow gravity vertical CENTER }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) .thenReturn(Gravity.CENTER) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -359,14 +381,14 @@ internal class ReactTextMapperTest { @Test fun `M fallback to view gravity W map { shadow gravity null }`() { // Given - whenever(mockReactTextShadowNodeUtils.getGravity(any())) - .thenReturn(null) + whenever(mockReactTextShadowNodeUtils.getGravity(any(), anyOrNull())) + .thenReturn(Gravity.CENTER) whenever(mockTextView.textAlignment).thenReturn(TextView.TEXT_ALIGNMENT_GRAVITY) - whenever(mockTextView.gravity).thenReturn(Gravity.CENTER) // When val wireframe = testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] + as MobileSegment.Wireframe.TextWireframe // Then val textPosition = wireframe.textPosition @@ -375,71 +397,4 @@ internal class ReactTextMapperTest { } // endregion - - // region text style - - @Test - fun `M fallback to textview font family W map { no shadow node }`(forge: Forge) { - // Given - val fakeTypeface = forge.aKeyFrom(typefaceMap) - whenever(mockTextView.typeface).thenReturn(fakeTypeface) - whenever(mockReactTextShadowNodeUtils.getShadowNode(any())).thenReturn(null) - - // When - val wireframe = - testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] - - // Then - val textStyle = wireframe.textStyle - assertThat(textStyle.family).isEqualTo(typefaceMap[fakeTypeface]) - } - - @Test - fun `M fallback to currentTextColor W map { no shadow node }`(forge: Forge) { - // Given - val fakeColor = 16711680 - whenever(mockTextView.currentTextColor).thenReturn(fakeColor) - whenever(mockReactTextShadowNodeUtils.getShadowNode(any())).thenReturn(null) - - // When - val wireframe = - testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] - - // Then - val textStyle = wireframe.textStyle - assertThat(textStyle.color).isEqualTo("#ff0000") - } - - @Test - fun `M fallback to view textSize W map { no shadow node }`(forge: Forge) { - // Given - val fakeTextSize = forge.aFloat() - whenever(mockTextView.textSize).thenReturn(fakeTextSize) - whenever(mockReactTextShadowNodeUtils.getShadowNode(any())).thenReturn(null) - - // When - val wireframe = - testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] - - // Then - val textStyle = wireframe.textStyle - assertThat(textStyle.size).isEqualTo(fakeTextSize.toLong()) - } - - @Test - fun `M default to SANS_SERIF W map { unknown typeface }`(@Mock unknownTypeface: Typeface) { - // Given - whenever(mockTextView.typeface).thenReturn(unknownTypeface) - whenever(mockReactTextShadowNodeUtils.getFontFamily(any())).thenReturn(null) - - // When - val wireframe = - testedMapper.map(mockTextView, mockMappingContext, mockAsyncJobStatusCallback)[0] - - // Then - val textStyle = wireframe.textStyle - assertThat(textStyle.family).isEqualTo(SANS_SERIF_FAMILY_NAME) - } - - // endregion }