@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.
- package/DatadogSDKReactNativeSessionReplay.podspec +41 -0
- package/README.md +3 -0
- package/android/build.gradle +239 -0
- package/android/detekt.yml +572 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +11 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DatadogSDKReactNativeSessionReplayPackage.kt +46 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementation.kt +57 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/NoopTextPropertiesResolver.kt +22 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupport.kt +77 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolver.kt +196 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplaySDKWrapper.kt +24 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/SessionReplayWrapper.kt +22 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/ShadowNodeWrapper.kt +70 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/TextPropertiesResolver.kt +20 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/extensions/LongExt.kt +15 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactMaskInputTextMapper.kt +54 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactMaskTextMapper.kt +55 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactTextMapper.kt +54 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapper.kt +58 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ColorUtils.kt +22 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtils.kt +35 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReactViewBackgroundDrawableUtils.kt +66 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReflectionUtils.kt +30 -0
- package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/TextViewUtils.kt +40 -0
- package/android/src/newarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +33 -0
- package/android/src/oldarch/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplay.kt +34 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/DdSessionReplayImplementationTest.kt +105 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactNativeSessionReplayExtensionSupportTest.kt +127 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/ReactTextPropertiesResolverTest.kt +271 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/mappers/ReactViewGroupMapperTest.kt +131 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/ColorUtilsTest.kt +42 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt +101 -0
- package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/TextViewUtilsTest.kt +109 -0
- package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/GenericAssert.kt +69 -0
- package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/MapExt.kt +29 -0
- package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/ReflectUtils.kt +266 -0
- package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/BaseConfigurator.kt +24 -0
- package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/ForgeConfigurator.kt +24 -0
- package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/TextWireframeForgeryFactory.kt +64 -0
- package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/Throwable.kt +31 -0
- package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/ThrowableForgeryFactory.kt +21 -0
- package/android/src/test/kotlin/com/datadog/reactnative/tools/unit/forge/WireframeClipForgeryFactory.kt +25 -0
- package/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker +1 -0
- package/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.pbxproj +272 -0
- package/ios/DatadogSDKReactNativeSessionReplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
- package/ios/Sources/DatadogSDKReactNativeSessionReplay.h +8 -0
- package/ios/Sources/DdSessionReplay.h +24 -0
- package/ios/Sources/DdSessionReplay.mm +53 -0
- package/ios/Sources/DdSessionReplayImplementation.swift +70 -0
- package/ios/Sources/RCTTextViewRecorder.swift +157 -0
- package/lib/commonjs/SessionReplay.js +66 -0
- package/lib/commonjs/SessionReplay.js.map +1 -0
- package/lib/commonjs/index.js +26 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/nativeModulesTypes.js +2 -0
- package/lib/commonjs/nativeModulesTypes.js.map +1 -0
- package/lib/commonjs/specs/NativeDdSessionReplay.js +20 -0
- package/lib/commonjs/specs/NativeDdSessionReplay.js.map +1 -0
- package/lib/module/SessionReplay.js +53 -0
- package/lib/module/SessionReplay.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/nativeModulesTypes.js +2 -0
- package/lib/module/nativeModulesTypes.js.map +1 -0
- package/lib/module/specs/NativeDdSessionReplay.js +14 -0
- package/lib/module/specs/NativeDdSessionReplay.js.map +1 -0
- package/lib/typescript/SessionReplay.d.ts +34 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/nativeModulesTypes.d.ts +18 -0
- package/lib/typescript/specs/NativeDdSessionReplay.d.ts +15 -0
- package/package.json +90 -0
- package/src/SessionReplay.ts +84 -0
- package/src/__tests__/SessionReplay.test.ts +49 -0
- package/src/index.ts +13 -0
- package/src/nativeModulesTypes.ts +29 -0
- 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
|
+
}
|
package/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/ReflectionUtils.kt
ADDED
|
@@ -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
|
+
}
|