@datadog/mobile-react-native-session-replay 2.0.2-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/DatadogSDKReactNativeSessionReplay.podspec +41 -0
  2. package/README.md +3 -0
  3. package/android/build.gradle +239 -0
  4. package/android/detekt.yml +572 -0
  5. package/android/gradle.properties +5 -0
  6. package/android/src/main/AndroidManifest.xml +11 -0
  7. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DatadogSDKReactNativeSessionReplayPackage.kt +46 -0
  8. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +57 -0
  9. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/NoopTextPropertiesResolver.kt +22 -0
  10. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.kt +77 -0
  11. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt +196 -0
  12. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt +24 -0
  13. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt +22 -0
  14. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ShadowNodeWrapper.kt +70 -0
  15. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/TextPropertiesResolver.kt +20 -0
  16. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/LongExt.kt +15 -0
  17. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactMaskInputTextMapper.kt +54 -0
  18. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactMaskTextMapper.kt +55 -0
  19. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapper.kt +54 -0
  20. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapper.kt +58 -0
  21. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ColorUtils.kt +22 -0
  22. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtils.kt +35 -0
  23. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +66 -0
  24. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReflectionUtils.kt +30 -0
  25. package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/TextViewUtils.kt +40 -0
  26. package/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +33 -0
  27. package/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +34 -0
  28. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt +105 -0
  29. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupportTest.kt +127 -0
  30. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolverTest.kt +271 -0
  31. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapperTest.kt +131 -0
  32. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/ColorUtilsTest.kt +42 -0
  33. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt +101 -0
  34. package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/TextViewUtilsTest.kt +109 -0
  35. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt +69 -0
  36. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/MapExt.kt +29 -0
  37. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/ReflectUtils.kt +266 -0
  38. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/BaseConfigurator.kt +24 -0
  39. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/ForgeConfigurator.kt +24 -0
  40. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/TextWireframeForgeryFactory.kt +64 -0
  41. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/Throwable.kt +31 -0
  42. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/ThrowableForgeryFactory.kt +21 -0
  43. package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/WireframeClipForgeryFactory.kt +25 -0
  44. package/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker +1 -0
  45. package/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.pbxproj +272 -0
  46. package/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
  47. package/ios/Sources/DatadogSDKReactNativeSessionReplay.h +8 -0
  48. package/ios/Sources/DdSessionReplay.h +24 -0
  49. package/ios/Sources/DdSessionReplay.mm +53 -0
  50. package/ios/Sources/DdSessionReplayImplementation.swift +70 -0
  51. package/ios/Sources/RCTTextViewRecorder.swift +157 -0
  52. package/lib/commonjs/SessionReplay.js +66 -0
  53. package/lib/commonjs/SessionReplay.js.map +1 -0
  54. package/lib/commonjs/index.js +26 -0
  55. package/lib/commonjs/index.js.map +1 -0
  56. package/lib/commonjs/nativeModulesTypes.js +2 -0
  57. package/lib/commonjs/nativeModulesTypes.js.map +1 -0
  58. package/lib/commonjs/specs/NativeDdSessionReplay.js +20 -0
  59. package/lib/commonjs/specs/NativeDdSessionReplay.js.map +1 -0
  60. package/lib/module/SessionReplay.js +53 -0
  61. package/lib/module/SessionReplay.js.map +1 -0
  62. package/lib/module/index.js +8 -0
  63. package/lib/module/index.js.map +1 -0
  64. package/lib/module/nativeModulesTypes.js +2 -0
  65. package/lib/module/nativeModulesTypes.js.map +1 -0
  66. package/lib/module/specs/NativeDdSessionReplay.js +14 -0
  67. package/lib/module/specs/NativeDdSessionReplay.js.map +1 -0
  68. package/lib/typescript/SessionReplay.d.ts +34 -0
  69. package/lib/typescript/index.d.ts +2 -0
  70. package/lib/typescript/nativeModulesTypes.d.ts +18 -0
  71. package/lib/typescript/specs/NativeDdSessionReplay.d.ts +15 -0
  72. package/package.json +90 -0
  73. package/src/SessionReplay.ts +84 -0
  74. package/src/__tests__/SessionReplay.test.ts +49 -0
  75. package/src/index.ts +13 -0
  76. package/src/nativeModulesTypes.ts +29 -0
  77. package/src/specs/NativeDdSessionReplay.ts +28 -0
@@ -0,0 +1,22 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay.utils
8
+
9
+ private const val HEX_COLOR_INCLUDING_ALPHA_LENGTH: Int = 8
10
+
11
+ internal fun formatAsRgba(backgroundColor: Int): String {
12
+ val colorHexString = Integer.toHexString(backgroundColor)
13
+ return "#${convertArgbToRgba(colorHexString)}"
14
+ }
15
+
16
+ private fun convertArgbToRgba(hexString: String): String {
17
+ return if (hexString.length == HEX_COLOR_INCLUDING_ALPHA_LENGTH) {
18
+ hexString.substring(2, 8) + hexString.substring(0, 2)
19
+ } else {
20
+ hexString
21
+ }
22
+ }
@@ -0,0 +1,35 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay.utils
8
+
9
+ import android.graphics.drawable.Drawable
10
+ import android.graphics.drawable.InsetDrawable
11
+ import android.graphics.drawable.LayerDrawable
12
+ import com.facebook.react.views.view.ReactViewBackgroundDrawable
13
+
14
+ internal class DrawableUtils {
15
+ internal fun getReactBackgroundFromDrawable(drawable: Drawable?): ReactViewBackgroundDrawable? {
16
+ if (drawable is ReactViewBackgroundDrawable) {
17
+ return drawable
18
+ }
19
+
20
+ if (drawable is InsetDrawable) {
21
+ return getReactBackgroundFromDrawable(drawable.drawable)
22
+ }
23
+
24
+ if (drawable is LayerDrawable) {
25
+ for (layerNumber in 0 until drawable.numberOfLayers) {
26
+ val layer = drawable.getDrawable(layerNumber)
27
+ if (layer is ReactViewBackgroundDrawable) {
28
+ return layer
29
+ }
30
+ }
31
+ }
32
+
33
+ return null
34
+ }
35
+ }
@@ -0,0 +1,66 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay.utils
8
+
9
+ import com.datadog.android.sessionreplay.model.MobileSegment
10
+ import com.datadog.reactnative.sessionreplay.extensions.convertToDensityNormalized
11
+ import com.facebook.react.uimanager.Spacing
12
+ import com.facebook.react.views.view.ReactViewBackgroundDrawable
13
+
14
+ internal class ReactViewBackgroundDrawableUtils(
15
+ private val reflectionUtils: ReflectionUtils = ReflectionUtils()
16
+ ) {
17
+ internal fun resolveShapeAndBorder(
18
+ drawable: ReactViewBackgroundDrawable,
19
+ opacity: Float,
20
+ pixelDensity: Float
21
+ ): Pair<MobileSegment.ShapeStyle?, MobileSegment.ShapeBorder?> {
22
+ val borderProps = resolveBorder(drawable, pixelDensity)
23
+ val backgroundColor = getBackgroundColor(drawable)
24
+ val colorHexString = if (backgroundColor != null) {
25
+ formatAsRgba(backgroundColor)
26
+ } else {
27
+ return null to borderProps
28
+ }
29
+
30
+ val cornerRadius =
31
+ drawable.fullBorderRadius.toLong().convertToDensityNormalized(pixelDensity)
32
+
33
+ return MobileSegment.ShapeStyle(
34
+ colorHexString,
35
+ opacity,
36
+ cornerRadius
37
+ ) to borderProps
38
+ }
39
+
40
+ private fun getBackgroundColor(
41
+ backgroundDrawable: ReactViewBackgroundDrawable,
42
+ ): Int? {
43
+ return reflectionUtils.getDeclaredField(
44
+ backgroundDrawable,
45
+ COLOR_FIELD_NAME
46
+ ) as Int?
47
+ }
48
+
49
+ private fun resolveBorder(
50
+ backgroundDrawable: ReactViewBackgroundDrawable,
51
+ pixelDensity: Float
52
+ ): MobileSegment.ShapeBorder {
53
+ val borderWidth =
54
+ backgroundDrawable.fullBorderWidth.toLong().convertToDensityNormalized(pixelDensity)
55
+ val borderColor = formatAsRgba(backgroundDrawable.getBorderColor(Spacing.ALL))
56
+
57
+ return MobileSegment.ShapeBorder(
58
+ color = borderColor,
59
+ width = borderWidth
60
+ )
61
+ }
62
+
63
+ private companion object {
64
+ private const val COLOR_FIELD_NAME = "mColor"
65
+ }
66
+ }
@@ -0,0 +1,30 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay.utils
8
+
9
+ import java.lang.reflect.Field
10
+
11
+ internal class ReflectionUtils {
12
+ internal fun getDeclaredField(instance: Any, fieldName: String): Any? {
13
+ val classInstance = instance.javaClass
14
+ val declaredField = searchForField(classInstance, fieldName)
15
+
16
+ return declaredField?.let {
17
+ it.isAccessible = true
18
+ it.get(instance)
19
+ }
20
+ }
21
+
22
+ private fun searchForField(className: Class<*>, fieldName: String): Field? {
23
+ return className.declaredFields.firstOrNull { it.name == fieldName }
24
+ ?: if (className.superclass != null) {
25
+ searchForField(className.superclass, fieldName)
26
+ } else {
27
+ null
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,40 @@
1
+ /*
2
+ *
3
+ * * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
4
+ * * This product includes software developed at Datadog (https://www.datadoghq.com/).
5
+ * * Copyright 2016-Present Datadog, Inc.
6
+ *
7
+ */
8
+
9
+ package com.datadog.reactnative.sessionreplay.utils
10
+
11
+ import android.widget.TextView
12
+ import com.datadog.android.sessionreplay.internal.recorder.MappingContext
13
+ import com.datadog.android.sessionreplay.model.MobileSegment
14
+ import com.datadog.reactnative.sessionreplay.TextPropertiesResolver
15
+
16
+ internal class TextViewUtils {
17
+ internal fun mapTextViewToWireframes(
18
+ wireframes: List<MobileSegment.Wireframe>,
19
+ view: TextView,
20
+ mappingContext: MappingContext,
21
+ reactTextPropertiesResolver: TextPropertiesResolver
22
+ ): List<MobileSegment.Wireframe> {
23
+ val result = mutableListOf<MobileSegment.Wireframe>()
24
+ val pixelDensity = mappingContext.systemInformation.screenDensity
25
+
26
+ wireframes.forEach { originalWireframe ->
27
+ if (originalWireframe !is MobileSegment.Wireframe.TextWireframe) {
28
+ result.add(originalWireframe)
29
+ } else {
30
+ result.add(reactTextPropertiesResolver.addReactNativeProperties(
31
+ originalWireframe = originalWireframe,
32
+ view = view,
33
+ pixelDensity = pixelDensity,
34
+ ))
35
+ }
36
+ }
37
+
38
+ return result
39
+ }
40
+ }
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay
8
+
9
+ import com.facebook.react.bridge.Promise
10
+ import com.facebook.react.bridge.ReactApplicationContext
11
+ import com.facebook.react.bridge.ReactMethod
12
+
13
+ /**
14
+ * The entry point to use Datadog's Session Replay feature.
15
+ */
16
+ class DdSessionReplay(
17
+ reactContext: ReactApplicationContext
18
+ ) : NativeDdSessionReplaySpec(reactContext) {
19
+
20
+ private val implementation = DdSessionReplayImplementation()
21
+
22
+ override fun getName(): String = DdSessionReplayImplementation.NAME
23
+
24
+ /**
25
+ * Enable session replay and start recording session.
26
+ * @param replaySampleRate The sample rate applied for session replay.
27
+ * @param defaultPrivacyLevel The privacy level used for replay.
28
+ */
29
+ @ReactMethod
30
+ override fun enable(replaySampleRate: Double, defaultPrivacyLevel: String, promise: Promise) {
31
+ implementation.enable(replaySampleRate, defaultPrivacyLevel, promise)
32
+ }
33
+ }
@@ -0,0 +1,34 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay
8
+
9
+ import com.facebook.react.bridge.Promise
10
+ import com.facebook.react.bridge.ReactApplicationContext
11
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
12
+ import com.facebook.react.bridge.ReactMethod
13
+
14
+ /**
15
+ * The entry point to use Datadog's Session Replay feature.
16
+ */
17
+ class DdSessionReplay(
18
+ reactContext: ReactApplicationContext
19
+ ) : ReactContextBaseJavaModule(reactContext) {
20
+
21
+ private val implementation = DdSessionReplayImplementation(reactContext)
22
+
23
+ override fun getName(): String = DdSessionReplayImplementation.NAME
24
+
25
+ /**
26
+ * Enable session replay and start recording session.
27
+ * @param replaySampleRate The sample rate applied for session replay.
28
+ * @param defaultPrivacyLevel The privacy level used for replay.
29
+ */
30
+ @ReactMethod
31
+ fun enable(replaySampleRate: Double, defaultPrivacyLevel: String, promise: Promise) {
32
+ implementation.enable(replaySampleRate, defaultPrivacyLevel, promise)
33
+ }
34
+ }
@@ -0,0 +1,105 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay
8
+
9
+ import com.datadog.android.sessionreplay.SessionReplayConfiguration
10
+ import com.datadog.android.sessionreplay.SessionReplayPrivacy
11
+ import com.datadog.tools.unit.GenericAssert.Companion.assertThat
12
+ import com.facebook.react.bridge.NativeModule
13
+ import com.facebook.react.bridge.Promise
14
+ import com.facebook.react.bridge.ReactContext
15
+ import com.facebook.react.uimanager.UIManagerModule
16
+ import fr.xgouchet.elmyr.annotation.DoubleForgery
17
+ import fr.xgouchet.elmyr.annotation.Forgery
18
+ import fr.xgouchet.elmyr.annotation.StringForgery
19
+ import fr.xgouchet.elmyr.junit5.ForgeExtension
20
+ import org.junit.jupiter.api.AfterEach
21
+ import org.junit.jupiter.api.BeforeEach
22
+ import org.junit.jupiter.api.Test
23
+ import org.junit.jupiter.api.extension.ExtendWith
24
+ import org.junit.jupiter.api.extension.Extensions
25
+ import org.mockito.Mock
26
+ import org.mockito.junit.jupiter.MockitoExtension
27
+ import org.mockito.junit.jupiter.MockitoSettings
28
+ import org.mockito.kotlin.any
29
+ import org.mockito.kotlin.argumentCaptor
30
+ import org.mockito.kotlin.doReturn
31
+ import org.mockito.kotlin.verify
32
+ import org.mockito.kotlin.whenever
33
+ import org.mockito.quality.Strictness
34
+
35
+ @Extensions(
36
+ ExtendWith(MockitoExtension::class),
37
+ ExtendWith(ForgeExtension::class)
38
+ )
39
+ @MockitoSettings(strictness = Strictness.LENIENT)
40
+ internal class DdSessionReplayImplementationTest {
41
+
42
+ lateinit var testedSessionReplay: DdSessionReplayImplementation
43
+
44
+ @Mock
45
+ lateinit var mockPromise: Promise
46
+
47
+ @Mock
48
+ lateinit var mockReactContext: ReactContext
49
+
50
+ @Mock
51
+ lateinit var mockSessionReplay: SessionReplayWrapper
52
+
53
+ @Mock
54
+ lateinit var mockUiManagerModule: UIManagerModule
55
+
56
+ @BeforeEach
57
+ fun `set up`() {
58
+ whenever(mockReactContext.getNativeModule(any<Class<NativeModule>>()))
59
+ .doReturn(mockUiManagerModule)
60
+
61
+ testedSessionReplay =
62
+ DdSessionReplayImplementation(mockReactContext) { mockSessionReplay }
63
+ }
64
+
65
+ @AfterEach
66
+ fun `tear down`() {
67
+ }
68
+
69
+ @Test
70
+ fun `M enable session replay W enable()`(
71
+ @DoubleForgery(min = 0.0, max = 100.0) replaySampleRate: Double,
72
+ @Forgery privacy: SessionReplayPrivacy
73
+ ) {
74
+ // Given
75
+ val sessionReplayConfigCaptor = argumentCaptor<SessionReplayConfiguration>()
76
+
77
+ // When
78
+ testedSessionReplay.enable(replaySampleRate, privacy.toString(), mockPromise)
79
+
80
+ // Then
81
+ verify(mockSessionReplay).enable(sessionReplayConfigCaptor.capture())
82
+ assertThat(sessionReplayConfigCaptor.firstValue)
83
+ .hasFieldEqualTo("sampleRate", replaySampleRate.toFloat())
84
+ .hasFieldEqualTo("privacy", privacy)
85
+ }
86
+
87
+ @Test
88
+ fun `M enable session replay with mask W enable with bad privacy option()`(
89
+ @DoubleForgery(min = 0.0, max = 100.0) replaySampleRate: Double,
90
+ // Not ALLOW nor MASK_USER_INPUT
91
+ @StringForgery(regex = "^/(?!ALLOW|MASK_USER_INPUT)([a-z0-9]+)$/i") privacy: String
92
+ ) {
93
+ // Given
94
+ val sessionReplayConfigCaptor = argumentCaptor<SessionReplayConfiguration>()
95
+
96
+ // When
97
+ testedSessionReplay.enable(replaySampleRate, privacy, mockPromise)
98
+
99
+ // Then
100
+ verify(mockSessionReplay).enable(sessionReplayConfigCaptor.capture())
101
+ assertThat(sessionReplayConfigCaptor.firstValue)
102
+ .hasFieldEqualTo("sampleRate", replaySampleRate.toFloat())
103
+ .hasFieldEqualTo("privacy", SessionReplayPrivacy.MASK)
104
+ }
105
+ }
@@ -0,0 +1,127 @@
1
+ /*
2
+ * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3
+ * This product includes software developed at Datadog (https://www.datadoghq.com/).
4
+ * Copyright 2016-Present Datadog, Inc.
5
+ */
6
+
7
+ package com.datadog.reactnative.sessionreplay
8
+
9
+ import com.datadog.android.api.InternalLogger
10
+ import com.datadog.android.sessionreplay.SessionReplayPrivacy
11
+ import com.datadog.reactnative.sessionreplay.mappers.ReactMaskInputTextMapper
12
+ import com.datadog.reactnative.sessionreplay.mappers.ReactMaskTextMapper
13
+ import com.datadog.reactnative.sessionreplay.mappers.ReactTextMapper
14
+ import com.datadog.reactnative.sessionreplay.mappers.ReactViewGroupMapper
15
+ import com.facebook.react.bridge.NativeModule
16
+ import com.facebook.react.bridge.ReactContext
17
+ import com.facebook.react.uimanager.UIManagerModule
18
+ import com.facebook.react.views.text.ReactTextView
19
+ import com.facebook.react.views.textinput.ReactEditText
20
+ import com.facebook.react.views.view.ReactViewGroup
21
+ import fr.xgouchet.elmyr.junit5.ForgeExtension
22
+ import org.assertj.core.api.Assertions.assertThat
23
+ import org.junit.jupiter.api.BeforeEach
24
+ import org.junit.jupiter.api.Test
25
+ import org.junit.jupiter.api.extension.ExtendWith
26
+ import org.junit.jupiter.api.extension.Extensions
27
+ import org.mockito.Mock
28
+ import org.mockito.junit.jupiter.MockitoExtension
29
+ import org.mockito.junit.jupiter.MockitoSettings
30
+ import org.mockito.kotlin.any
31
+ import org.mockito.kotlin.doReturn
32
+ import org.mockito.kotlin.whenever
33
+ import org.mockito.quality.Strictness
34
+
35
+ @Extensions(
36
+ ExtendWith(MockitoExtension::class),
37
+ ExtendWith(ForgeExtension::class)
38
+ )
39
+ @MockitoSettings(strictness = Strictness.LENIENT)
40
+ internal class ReactNativeSessionReplayExtensionSupportTest {
41
+
42
+ @Mock
43
+ private lateinit var mockReactContext: ReactContext
44
+
45
+ @Mock
46
+ private lateinit var mockUiManagerModule: UIManagerModule
47
+
48
+ @Mock
49
+ private lateinit var mockLogger: InternalLogger
50
+
51
+ private lateinit var testedExtensionSupport: ReactNativeSessionReplayExtensionSupport
52
+
53
+ @BeforeEach
54
+ fun `set up`() {
55
+ whenever(mockReactContext.getNativeModule(any<Class<NativeModule>>()))
56
+ .doReturn(mockUiManagerModule)
57
+
58
+ testedExtensionSupport = ReactNativeSessionReplayExtensionSupport(
59
+ logger = mockLogger,
60
+ reactContext = mockReactContext
61
+ )
62
+ }
63
+
64
+ @Test
65
+ fun `M get custom view mappers W getCustomViewMappers()`() {
66
+ // When
67
+ val customViewMappers = testedExtensionSupport.getCustomViewMappers()
68
+ val allowMappers = customViewMappers[SessionReplayPrivacy.ALLOW]
69
+
70
+ // Then
71
+ check(allowMappers != null)
72
+ assertThat(allowMappers).hasSize(3)
73
+ assertThat(allowMappers[ReactViewGroup::class.java])
74
+ .isInstanceOf(ReactViewGroupMapper::class.java)
75
+ assertThat(allowMappers[ReactTextView::class.java])
76
+ .isInstanceOf(ReactTextMapper::class.java)
77
+ assertThat(allowMappers[ReactEditText::class.java])
78
+ .isInstanceOf(ReactTextMapper::class.java)
79
+ }
80
+
81
+ @Test
82
+ fun `M get mask input mappers W getCustomViewMappers()`() {
83
+ // When
84
+ val customViewMappers = testedExtensionSupport.getCustomViewMappers()
85
+ val maskUserInputMappers = customViewMappers[SessionReplayPrivacy.MASK_USER_INPUT]
86
+
87
+ // Then
88
+ check(maskUserInputMappers != null)
89
+ assertThat(maskUserInputMappers).hasSize(3)
90
+ assertThat(maskUserInputMappers[ReactViewGroup::class.java])
91
+ .isInstanceOf(ReactViewGroupMapper::class.java)
92
+ assertThat(maskUserInputMappers[ReactTextView::class.java])
93
+ .isInstanceOf(ReactMaskInputTextMapper::class.java)
94
+ assertThat(maskUserInputMappers[ReactEditText::class.java])
95
+ .isInstanceOf(ReactMaskInputTextMapper::class.java)
96
+ }
97
+
98
+ @Test
99
+ fun `M get mask mappers W getCustomViewMappers()`() {
100
+ // When
101
+ val customViewMappers = testedExtensionSupport.getCustomViewMappers()
102
+ val maskMappers = customViewMappers[SessionReplayPrivacy.MASK]
103
+
104
+ // Then
105
+ check(maskMappers != null)
106
+ assertThat(maskMappers).hasSize(3)
107
+ assertThat(maskMappers[ReactViewGroup::class.java])
108
+ .isInstanceOf(ReactViewGroupMapper::class.java)
109
+ assertThat(maskMappers[ReactTextView::class.java])
110
+ .isInstanceOf(ReactMaskTextMapper::class.java)
111
+ assertThat(maskMappers[ReactEditText::class.java])
112
+ .isInstanceOf(ReactMaskTextMapper::class.java)
113
+ }
114
+
115
+ @Test
116
+ fun `M return null W getUiManagerModule() { cannot get uiManagerModule }`() {
117
+ // Given
118
+ whenever(mockReactContext.getNativeModule(any<Class<NativeModule>>()))
119
+ .thenThrow(IllegalStateException())
120
+
121
+ // When
122
+ val uiManagerModule = testedExtensionSupport.getUiManagerModule()
123
+
124
+ // Then
125
+ assertThat(uiManagerModule).isNull()
126
+ }
127
+ }