@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,271 @@
|
|
|
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 android.widget.TextView
|
|
10
|
+
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
11
|
+
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Companion.COLOR_FIELD_NAME
|
|
12
|
+
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Companion.FONT_FAMILY_FIELD_NAME
|
|
13
|
+
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Companion.MONOSPACE_FAMILY_NAME
|
|
14
|
+
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver.Companion.TEXT_ATTRIBUTES_FIELD_NAME
|
|
15
|
+
import com.datadog.reactnative.sessionreplay.ShadowNodeWrapper.Companion.UI_IMPLEMENTATION_FIELD_NAME
|
|
16
|
+
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
17
|
+
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
|
|
18
|
+
import com.datadog.reactnative.sessionreplay.utils.ReflectionUtils
|
|
19
|
+
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
|
|
20
|
+
import com.datadog.reactnative.tools.unit.forge.ForgeConfigurator
|
|
21
|
+
import com.facebook.react.bridge.ReactContext
|
|
22
|
+
import com.facebook.react.uimanager.ReactShadowNode
|
|
23
|
+
import com.facebook.react.uimanager.UIImplementation
|
|
24
|
+
import com.facebook.react.uimanager.UIManagerModule
|
|
25
|
+
import com.facebook.react.views.text.TextAttributes
|
|
26
|
+
import com.facebook.react.views.view.ReactViewBackgroundDrawable
|
|
27
|
+
import fr.xgouchet.elmyr.Forge
|
|
28
|
+
import fr.xgouchet.elmyr.annotation.Forgery
|
|
29
|
+
import fr.xgouchet.elmyr.annotation.IntForgery
|
|
30
|
+
import fr.xgouchet.elmyr.junit5.ForgeConfiguration
|
|
31
|
+
import fr.xgouchet.elmyr.junit5.ForgeExtension
|
|
32
|
+
import org.assertj.core.api.Assertions.assertThat
|
|
33
|
+
import org.junit.jupiter.api.BeforeEach
|
|
34
|
+
import org.junit.jupiter.api.Test
|
|
35
|
+
import org.junit.jupiter.api.extension.ExtendWith
|
|
36
|
+
import org.junit.jupiter.api.extension.Extensions
|
|
37
|
+
import org.mockito.Mock
|
|
38
|
+
import org.mockito.junit.jupiter.MockitoExtension
|
|
39
|
+
import org.mockito.junit.jupiter.MockitoSettings
|
|
40
|
+
import org.mockito.kotlin.any
|
|
41
|
+
import org.mockito.kotlin.eq
|
|
42
|
+
import org.mockito.kotlin.whenever
|
|
43
|
+
import org.mockito.quality.Strictness
|
|
44
|
+
|
|
45
|
+
@Extensions(
|
|
46
|
+
ExtendWith(MockitoExtension::class),
|
|
47
|
+
ExtendWith(ForgeExtension::class)
|
|
48
|
+
)
|
|
49
|
+
@MockitoSettings(strictness = Strictness.LENIENT)
|
|
50
|
+
@ForgeConfiguration(ForgeConfigurator::class)
|
|
51
|
+
internal class ReactTextPropertiesResolverTest {
|
|
52
|
+
private lateinit var testedResolver: ReactTextPropertiesResolver
|
|
53
|
+
|
|
54
|
+
@Mock
|
|
55
|
+
lateinit var mockReactContext: ReactContext
|
|
56
|
+
|
|
57
|
+
@Mock
|
|
58
|
+
lateinit var mockUiManagerModule: UIManagerModule
|
|
59
|
+
|
|
60
|
+
@Mock
|
|
61
|
+
lateinit var mockWireframe: MobileSegment.Wireframe.TextWireframe
|
|
62
|
+
|
|
63
|
+
@Mock
|
|
64
|
+
lateinit var mockTextView: TextView
|
|
65
|
+
|
|
66
|
+
@Mock
|
|
67
|
+
lateinit var mockDrawableUtils: DrawableUtils
|
|
68
|
+
|
|
69
|
+
@Mock
|
|
70
|
+
lateinit var mockReactViewBackgroundDrawable: ReactViewBackgroundDrawable
|
|
71
|
+
|
|
72
|
+
@Mock
|
|
73
|
+
lateinit var mockReactViewBackgroundDrawableUtils: ReactViewBackgroundDrawableUtils
|
|
74
|
+
|
|
75
|
+
@Mock
|
|
76
|
+
lateinit var mockShadowNodeWrapper: ShadowNodeWrapper
|
|
77
|
+
|
|
78
|
+
@Mock
|
|
79
|
+
lateinit var mockReflectionUtils: ReflectionUtils
|
|
80
|
+
|
|
81
|
+
@Forgery
|
|
82
|
+
private lateinit var fakeWireframe: MobileSegment.Wireframe.TextWireframe
|
|
83
|
+
|
|
84
|
+
@Mock
|
|
85
|
+
private lateinit var mockUiImplementation: UIImplementation
|
|
86
|
+
|
|
87
|
+
@Mock
|
|
88
|
+
private lateinit var mockShadowNode: ReactShadowNode<out ReactShadowNode<*>>
|
|
89
|
+
|
|
90
|
+
@BeforeEach
|
|
91
|
+
fun `set up`(forge: Forge) {
|
|
92
|
+
whenever(
|
|
93
|
+
mockReflectionUtils.getDeclaredField(mockUiManagerModule, UI_IMPLEMENTATION_FIELD_NAME)
|
|
94
|
+
).thenReturn(mockUiImplementation)
|
|
95
|
+
|
|
96
|
+
whenever(
|
|
97
|
+
mockUiImplementation.resolveShadowNode(
|
|
98
|
+
eq(mockTextView.id)
|
|
99
|
+
)
|
|
100
|
+
).thenReturn(mockShadowNode)
|
|
101
|
+
|
|
102
|
+
whenever(mockReactContext.runOnNativeModulesQueueThread(any())).thenAnswer {
|
|
103
|
+
(it.arguments[0] as Runnable).run()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
testedResolver = ReactTextPropertiesResolver(
|
|
107
|
+
reactContext = mockReactContext,
|
|
108
|
+
uiManagerModule = mockUiManagerModule,
|
|
109
|
+
reactViewBackgroundDrawableUtils = mockReactViewBackgroundDrawableUtils,
|
|
110
|
+
drawableUtils = mockDrawableUtils,
|
|
111
|
+
reflectionUtils = mockReflectionUtils
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// region addReactNativeProperties
|
|
116
|
+
|
|
117
|
+
@Test
|
|
118
|
+
fun `M get original wireframe W addReactNativeProperties() { no react properties }`() {
|
|
119
|
+
// Given
|
|
120
|
+
whenever(mockTextView.background).thenReturn(null)
|
|
121
|
+
whenever(mockUiImplementation.resolveShadowNode(any())).thenReturn(null)
|
|
122
|
+
|
|
123
|
+
// When
|
|
124
|
+
val result = testedResolver.addReactNativeProperties(mockWireframe, mockTextView, 0f)
|
|
125
|
+
|
|
126
|
+
// Then
|
|
127
|
+
assertThat(result).isEqualTo(mockWireframe)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@Test
|
|
131
|
+
fun `M add drawable properties W addReactNativeProperties() { has reactBackgroundDrawable }`(
|
|
132
|
+
forge: Forge
|
|
133
|
+
) {
|
|
134
|
+
// Given
|
|
135
|
+
val pixelDensity = 0f
|
|
136
|
+
val fakeBorderRadius = forge.aPositiveFloat()
|
|
137
|
+
val fakeBorderWidth = forge.aPositiveFloat()
|
|
138
|
+
val fakeBorderColor = forge.aPositiveInt()
|
|
139
|
+
whenever(mockTextView.background).thenReturn(mockReactViewBackgroundDrawable)
|
|
140
|
+
whenever(
|
|
141
|
+
mockDrawableUtils.getReactBackgroundFromDrawable(
|
|
142
|
+
eq(mockTextView.background)
|
|
143
|
+
)
|
|
144
|
+
).thenReturn(mockReactViewBackgroundDrawable)
|
|
145
|
+
whenever(
|
|
146
|
+
mockReactViewBackgroundDrawableUtils.resolveShapeAndBorder(
|
|
147
|
+
drawable = eq(mockReactViewBackgroundDrawable),
|
|
148
|
+
opacity = eq(0f),
|
|
149
|
+
pixelDensity = eq(0f)
|
|
150
|
+
)
|
|
151
|
+
).thenReturn(
|
|
152
|
+
MobileSegment.ShapeStyle(
|
|
153
|
+
backgroundColor = formatAsRgba(fakeBorderColor),
|
|
154
|
+
opacity = 0f,
|
|
155
|
+
cornerRadius = fakeBorderRadius.toLong()
|
|
156
|
+
) to MobileSegment.ShapeBorder(
|
|
157
|
+
color = formatAsRgba(fakeBorderColor),
|
|
158
|
+
width = fakeBorderWidth.toLong()
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
// When
|
|
163
|
+
val result = testedResolver.addReactNativeProperties(
|
|
164
|
+
fakeWireframe,
|
|
165
|
+
mockTextView,
|
|
166
|
+
pixelDensity
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
// Then
|
|
170
|
+
assertThat(result.shapeStyle?.cornerRadius).isEqualTo(fakeBorderRadius.toLong())
|
|
171
|
+
assertThat(result.border?.width).isEqualTo(fakeBorderWidth.toLong())
|
|
172
|
+
assertThat(result.border?.color).isEqualTo(formatAsRgba(fakeBorderColor))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@Test
|
|
176
|
+
fun `M resolve font family W addReactNativeProperties()`() {
|
|
177
|
+
// Given
|
|
178
|
+
whenever(mockReflectionUtils.getDeclaredField(mockShadowNode, FONT_FAMILY_FIELD_NAME))
|
|
179
|
+
.thenReturn(MONOSPACE_FAMILY_NAME)
|
|
180
|
+
|
|
181
|
+
whenever(mockTextView.background).thenReturn(null)
|
|
182
|
+
|
|
183
|
+
// When
|
|
184
|
+
val result = testedResolver
|
|
185
|
+
.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
186
|
+
|
|
187
|
+
// Then
|
|
188
|
+
assertThat(result.textStyle.family)
|
|
189
|
+
.isEqualTo(MONOSPACE_FAMILY_NAME)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@Test
|
|
193
|
+
fun `M fallback W addReactNativeProperties() { cannot resolve fontFamily }`() {
|
|
194
|
+
// Given
|
|
195
|
+
whenever(mockTextView.background).thenReturn(null)
|
|
196
|
+
whenever(mockShadowNodeWrapper.getDeclaredShadowNodeField(FONT_FAMILY_FIELD_NAME))
|
|
197
|
+
.thenReturn(null)
|
|
198
|
+
|
|
199
|
+
// When
|
|
200
|
+
val result = testedResolver.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
201
|
+
|
|
202
|
+
// Then
|
|
203
|
+
assertThat(result.textStyle.family).isEqualTo(fakeWireframe.textStyle.family)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Test
|
|
207
|
+
fun `M resolve font size W addReactNativeProperties()`(
|
|
208
|
+
@Mock mockTextAttributes: TextAttributes,
|
|
209
|
+
@IntForgery fakeTextSize: Int
|
|
210
|
+
) {
|
|
211
|
+
// Given
|
|
212
|
+
whenever(mockReflectionUtils.getDeclaredField(mockShadowNode, TEXT_ATTRIBUTES_FIELD_NAME))
|
|
213
|
+
.thenReturn(mockTextAttributes)
|
|
214
|
+
whenever(mockTextView.background).thenReturn(null)
|
|
215
|
+
whenever(mockTextAttributes.effectiveFontSize).thenReturn(fakeTextSize)
|
|
216
|
+
|
|
217
|
+
// When
|
|
218
|
+
val result = testedResolver.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
219
|
+
|
|
220
|
+
// Then
|
|
221
|
+
assertThat(result.textStyle.size).isEqualTo(fakeTextSize.toLong())
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@Test
|
|
225
|
+
fun `M fallback W addReactNativeProperties() { cannot resolve fontSize }`(
|
|
226
|
+
@Mock mockTextAttributes: TextAttributes
|
|
227
|
+
) {
|
|
228
|
+
// Given
|
|
229
|
+
whenever(mockTextView.background).thenReturn(null)
|
|
230
|
+
whenever(mockShadowNodeWrapper.getDeclaredShadowNodeField(TEXT_ATTRIBUTES_FIELD_NAME))
|
|
231
|
+
.thenReturn(null)
|
|
232
|
+
|
|
233
|
+
// When
|
|
234
|
+
val result = testedResolver.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
235
|
+
|
|
236
|
+
// Then
|
|
237
|
+
assertThat(result.textStyle.size).isEqualTo(fakeWireframe.textStyle.size)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@Test
|
|
241
|
+
fun `M resolve font color W addReactNativeProperties()`(
|
|
242
|
+
@IntForgery fakeTextColor: Int
|
|
243
|
+
) {
|
|
244
|
+
// Given
|
|
245
|
+
whenever(mockTextView.background).thenReturn(null)
|
|
246
|
+
whenever(mockReflectionUtils.getDeclaredField(mockShadowNode, COLOR_FIELD_NAME))
|
|
247
|
+
.thenReturn(fakeTextColor)
|
|
248
|
+
|
|
249
|
+
// When
|
|
250
|
+
val result = testedResolver.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
251
|
+
|
|
252
|
+
// Then
|
|
253
|
+
assertThat(result.textStyle.color).isEqualTo(formatAsRgba(fakeTextColor))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@Test
|
|
257
|
+
fun `M fallback W addReactNativeProperties() { cannot resolve fontColor }`() {
|
|
258
|
+
// Given
|
|
259
|
+
whenever(mockTextView.background).thenReturn(null)
|
|
260
|
+
whenever(mockShadowNodeWrapper.getDeclaredShadowNodeField(COLOR_FIELD_NAME))
|
|
261
|
+
.thenReturn(null)
|
|
262
|
+
|
|
263
|
+
// When
|
|
264
|
+
val result = testedResolver.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
|
|
265
|
+
|
|
266
|
+
// Then
|
|
267
|
+
assertThat(result.textStyle.color).isEqualTo(fakeWireframe.textStyle.color)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// endregion
|
|
271
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
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.mappers
|
|
10
|
+
|
|
11
|
+
import android.graphics.drawable.ColorDrawable
|
|
12
|
+
import com.datadog.android.sessionreplay.internal.AsyncJobStatusCallback
|
|
13
|
+
import com.datadog.android.sessionreplay.internal.recorder.MappingContext
|
|
14
|
+
import com.datadog.android.sessionreplay.internal.recorder.SystemInformation
|
|
15
|
+
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
16
|
+
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
|
|
17
|
+
import com.datadog.reactnative.sessionreplay.utils.ReactViewBackgroundDrawableUtils
|
|
18
|
+
import com.facebook.react.views.view.ReactViewBackgroundDrawable
|
|
19
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
20
|
+
import fr.xgouchet.elmyr.junit5.ForgeExtension
|
|
21
|
+
import org.assertj.core.api.Assertions.assertThat
|
|
22
|
+
import org.junit.jupiter.api.BeforeEach
|
|
23
|
+
import org.junit.jupiter.api.Test
|
|
24
|
+
import org.junit.jupiter.api.extension.ExtendWith
|
|
25
|
+
import org.junit.jupiter.api.extension.Extensions
|
|
26
|
+
import org.mockito.Mock
|
|
27
|
+
import org.mockito.junit.jupiter.MockitoExtension
|
|
28
|
+
import org.mockito.junit.jupiter.MockitoSettings
|
|
29
|
+
import org.mockito.kotlin.eq
|
|
30
|
+
import org.mockito.kotlin.whenever
|
|
31
|
+
import org.mockito.quality.Strictness
|
|
32
|
+
|
|
33
|
+
@Extensions(
|
|
34
|
+
ExtendWith(MockitoExtension::class),
|
|
35
|
+
ExtendWith(ForgeExtension::class)
|
|
36
|
+
)
|
|
37
|
+
@MockitoSettings(strictness = Strictness.LENIENT)
|
|
38
|
+
internal class ReactViewGroupMapperTest {
|
|
39
|
+
private lateinit var testedMapper: ReactViewGroupMapper
|
|
40
|
+
|
|
41
|
+
@Mock
|
|
42
|
+
private lateinit var mockReactViewBackgroundDrawableUtils: ReactViewBackgroundDrawableUtils
|
|
43
|
+
|
|
44
|
+
@Mock
|
|
45
|
+
private lateinit var mockReactViewGroup: ReactViewGroup
|
|
46
|
+
|
|
47
|
+
@Mock
|
|
48
|
+
private lateinit var mockMappingContext: MappingContext
|
|
49
|
+
|
|
50
|
+
@Mock
|
|
51
|
+
private lateinit var mockAsyncJobStatusCallback: AsyncJobStatusCallback
|
|
52
|
+
|
|
53
|
+
@Mock
|
|
54
|
+
private lateinit var mockSystemInformation: SystemInformation
|
|
55
|
+
|
|
56
|
+
@Mock
|
|
57
|
+
private lateinit var mockDrawableUtils: DrawableUtils
|
|
58
|
+
|
|
59
|
+
@Mock
|
|
60
|
+
private lateinit var mockReactViewBackgroundDrawable: ReactViewBackgroundDrawable
|
|
61
|
+
|
|
62
|
+
@Mock
|
|
63
|
+
private lateinit var mockShapeStyle: MobileSegment.ShapeStyle
|
|
64
|
+
|
|
65
|
+
@Mock
|
|
66
|
+
private lateinit var mockShapeBorder: MobileSegment.ShapeBorder
|
|
67
|
+
|
|
68
|
+
@BeforeEach
|
|
69
|
+
fun `set up`() {
|
|
70
|
+
whenever(mockMappingContext.systemInformation).thenReturn(mockSystemInformation)
|
|
71
|
+
whenever(mockSystemInformation.screenDensity).thenReturn(0f)
|
|
72
|
+
|
|
73
|
+
testedMapper = ReactViewGroupMapper(
|
|
74
|
+
reactViewBackgroundDrawableUtils = mockReactViewBackgroundDrawableUtils,
|
|
75
|
+
drawableUtils = mockDrawableUtils
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@Test
|
|
80
|
+
fun `M return null shapestyle + border W map() { other drawable }`(
|
|
81
|
+
@Mock mockColorDrawable: ColorDrawable
|
|
82
|
+
) {
|
|
83
|
+
// Given
|
|
84
|
+
whenever(mockReactViewGroup.background).thenReturn(mockColorDrawable)
|
|
85
|
+
whenever(
|
|
86
|
+
mockDrawableUtils.getReactBackgroundFromDrawable(
|
|
87
|
+
drawable = eq(mockColorDrawable)
|
|
88
|
+
)
|
|
89
|
+
).thenReturn(null)
|
|
90
|
+
|
|
91
|
+
// When
|
|
92
|
+
val result = testedMapper.map(
|
|
93
|
+
view = mockReactViewGroup,
|
|
94
|
+
mappingContext = mockMappingContext,
|
|
95
|
+
asyncJobStatusCallback = mockAsyncJobStatusCallback
|
|
96
|
+
)[0] as MobileSegment.Wireframe.ShapeWireframe
|
|
97
|
+
|
|
98
|
+
// Then
|
|
99
|
+
assertThat(result.shapeStyle).isNull()
|
|
100
|
+
assertThat(result.border).isNull()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Test
|
|
104
|
+
fun `M return shapestyle + border W map() { ReactViewBackgroundDrawable }`() {
|
|
105
|
+
// Given
|
|
106
|
+
whenever(mockReactViewGroup.background).thenReturn(mockReactViewBackgroundDrawable)
|
|
107
|
+
whenever(
|
|
108
|
+
mockDrawableUtils.getReactBackgroundFromDrawable(
|
|
109
|
+
drawable = eq(mockReactViewBackgroundDrawable)
|
|
110
|
+
)
|
|
111
|
+
).thenReturn(mockReactViewBackgroundDrawable)
|
|
112
|
+
whenever(
|
|
113
|
+
mockReactViewBackgroundDrawableUtils.resolveShapeAndBorder(
|
|
114
|
+
drawable = eq(mockReactViewBackgroundDrawable),
|
|
115
|
+
pixelDensity = eq(0f),
|
|
116
|
+
opacity = eq(0f)
|
|
117
|
+
)
|
|
118
|
+
).thenReturn(mockShapeStyle to mockShapeBorder)
|
|
119
|
+
|
|
120
|
+
// When
|
|
121
|
+
val result = testedMapper.map(
|
|
122
|
+
view = mockReactViewGroup,
|
|
123
|
+
mappingContext = mockMappingContext,
|
|
124
|
+
asyncJobStatusCallback = mockAsyncJobStatusCallback
|
|
125
|
+
)[0] as MobileSegment.Wireframe.ShapeWireframe
|
|
126
|
+
|
|
127
|
+
// Then
|
|
128
|
+
assertThat(result.shapeStyle).isEqualTo(mockShapeStyle)
|
|
129
|
+
assertThat(result.border).isEqualTo(mockShapeBorder)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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 fr.xgouchet.elmyr.junit5.ForgeExtension
|
|
10
|
+
import org.assertj.core.api.Assertions.assertThat
|
|
11
|
+
import org.junit.jupiter.api.Test
|
|
12
|
+
import org.junit.jupiter.api.extension.ExtendWith
|
|
13
|
+
import org.junit.jupiter.api.extension.Extensions
|
|
14
|
+
import org.mockito.junit.jupiter.MockitoExtension
|
|
15
|
+
import org.mockito.junit.jupiter.MockitoSettings
|
|
16
|
+
import org.mockito.quality.Strictness
|
|
17
|
+
|
|
18
|
+
@Extensions(
|
|
19
|
+
ExtendWith(MockitoExtension::class),
|
|
20
|
+
ExtendWith(ForgeExtension::class)
|
|
21
|
+
)
|
|
22
|
+
@MockitoSettings(strictness = Strictness.LENIENT)
|
|
23
|
+
internal class ColorUtilsTest {
|
|
24
|
+
|
|
25
|
+
@Test
|
|
26
|
+
fun `M return without alpha W formatAsRgba { color without alpha }`() {
|
|
27
|
+
// When
|
|
28
|
+
val hexColor = formatAsRgba(16711680)
|
|
29
|
+
|
|
30
|
+
// Then
|
|
31
|
+
assertThat(hexColor).isEqualTo("#ff0000")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Test
|
|
35
|
+
fun `M resolve with alpha W formatAsRgba { color with alpha }`() {
|
|
36
|
+
// When
|
|
37
|
+
val hexColor = formatAsRgba(1717960806)
|
|
38
|
+
|
|
39
|
+
// Then
|
|
40
|
+
assertThat(hexColor).isEqualTo("#66006666")
|
|
41
|
+
}
|
|
42
|
+
}
|
package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/DrawableUtilsTest.kt
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
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.ColorDrawable
|
|
10
|
+
import android.graphics.drawable.InsetDrawable
|
|
11
|
+
import android.graphics.drawable.LayerDrawable
|
|
12
|
+
import com.facebook.react.views.view.ReactViewBackgroundDrawable
|
|
13
|
+
import fr.xgouchet.elmyr.junit5.ForgeExtension
|
|
14
|
+
import org.assertj.core.api.Assertions.assertThat
|
|
15
|
+
import org.junit.jupiter.api.BeforeEach
|
|
16
|
+
import org.junit.jupiter.api.Test
|
|
17
|
+
import org.junit.jupiter.api.extension.ExtendWith
|
|
18
|
+
import org.junit.jupiter.api.extension.Extensions
|
|
19
|
+
import org.mockito.Mock
|
|
20
|
+
import org.mockito.junit.jupiter.MockitoExtension
|
|
21
|
+
import org.mockito.junit.jupiter.MockitoSettings
|
|
22
|
+
import org.mockito.kotlin.whenever
|
|
23
|
+
import org.mockito.quality.Strictness
|
|
24
|
+
|
|
25
|
+
@Extensions(
|
|
26
|
+
ExtendWith(MockitoExtension::class),
|
|
27
|
+
ExtendWith(ForgeExtension::class)
|
|
28
|
+
)
|
|
29
|
+
@MockitoSettings(strictness = Strictness.LENIENT)
|
|
30
|
+
internal class DrawableUtilsTest {
|
|
31
|
+
private lateinit var testedDrawableUtils: DrawableUtils
|
|
32
|
+
|
|
33
|
+
@Mock
|
|
34
|
+
private lateinit var mockReactViewBackgroundDrawable: ReactViewBackgroundDrawable
|
|
35
|
+
|
|
36
|
+
@Mock
|
|
37
|
+
private lateinit var mockInsetDrawable: InsetDrawable
|
|
38
|
+
|
|
39
|
+
@Mock
|
|
40
|
+
private lateinit var mockLayerDrawable: LayerDrawable
|
|
41
|
+
|
|
42
|
+
@Mock
|
|
43
|
+
private lateinit var mockColorDrawable: ColorDrawable
|
|
44
|
+
|
|
45
|
+
@BeforeEach
|
|
46
|
+
fun `set up`() {
|
|
47
|
+
whenever(mockLayerDrawable.numberOfLayers).thenReturn(3)
|
|
48
|
+
whenever(mockLayerDrawable.getDrawable(0)).thenReturn(mockReactViewBackgroundDrawable)
|
|
49
|
+
|
|
50
|
+
testedDrawableUtils = DrawableUtils()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Test
|
|
54
|
+
fun `M return drawable W getReactBackgroundFromDrawable { ReactViewBackgroundDrawable }`() {
|
|
55
|
+
// When
|
|
56
|
+
val result = testedDrawableUtils
|
|
57
|
+
.getReactBackgroundFromDrawable(mockReactViewBackgroundDrawable)
|
|
58
|
+
|
|
59
|
+
// Then
|
|
60
|
+
assertThat(result).isEqualTo(mockReactViewBackgroundDrawable)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Test
|
|
64
|
+
fun `M return drawable W getReactBackgroundFromDrawable { InsetDrawable }`() {
|
|
65
|
+
// Given
|
|
66
|
+
whenever(mockInsetDrawable.drawable).thenReturn(mockReactViewBackgroundDrawable)
|
|
67
|
+
|
|
68
|
+
// When
|
|
69
|
+
val result = testedDrawableUtils.getReactBackgroundFromDrawable(mockInsetDrawable)
|
|
70
|
+
|
|
71
|
+
// Then
|
|
72
|
+
assertThat(result).isEqualTo(mockReactViewBackgroundDrawable)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Test
|
|
76
|
+
fun `M return drawable W getReactBackgroundFromDrawable { LayerDrawable }`() {
|
|
77
|
+
// When
|
|
78
|
+
val result = testedDrawableUtils.getReactBackgroundFromDrawable(mockLayerDrawable)
|
|
79
|
+
|
|
80
|
+
// Then
|
|
81
|
+
assertThat(result).isEqualTo(mockReactViewBackgroundDrawable)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Test
|
|
85
|
+
fun `M return null W getReactBackgroundFromDrawable { drawable is not supported }`() {
|
|
86
|
+
// When
|
|
87
|
+
val result = testedDrawableUtils.getReactBackgroundFromDrawable(mockColorDrawable)
|
|
88
|
+
|
|
89
|
+
// Then
|
|
90
|
+
assertThat(result).isNull()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Test
|
|
94
|
+
fun `M return null W getReactBackgroundFromDrawable { null drawable }`() {
|
|
95
|
+
// When
|
|
96
|
+
val result = testedDrawableUtils.getReactBackgroundFromDrawable(null)
|
|
97
|
+
|
|
98
|
+
// Then
|
|
99
|
+
assertThat(result).isNull()
|
|
100
|
+
}
|
|
101
|
+
}
|
package/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/TextViewUtilsTest.kt
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
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.content.res.Resources
|
|
10
|
+
import android.graphics.Typeface
|
|
11
|
+
import android.util.DisplayMetrics
|
|
12
|
+
import android.widget.TextView
|
|
13
|
+
import com.datadog.android.sessionreplay.internal.recorder.MappingContext
|
|
14
|
+
import com.datadog.android.sessionreplay.internal.recorder.SystemInformation
|
|
15
|
+
import com.datadog.android.sessionreplay.model.MobileSegment
|
|
16
|
+
import com.datadog.reactnative.sessionreplay.ReactTextPropertiesResolver
|
|
17
|
+
import fr.xgouchet.elmyr.Forge
|
|
18
|
+
import fr.xgouchet.elmyr.junit5.ForgeExtension
|
|
19
|
+
import org.assertj.core.api.Assertions.assertThat
|
|
20
|
+
import org.junit.jupiter.api.BeforeEach
|
|
21
|
+
import org.junit.jupiter.api.Test
|
|
22
|
+
import org.junit.jupiter.api.extension.ExtendWith
|
|
23
|
+
import org.junit.jupiter.api.extension.Extensions
|
|
24
|
+
import org.mockito.Mock
|
|
25
|
+
import org.mockito.junit.jupiter.MockitoExtension
|
|
26
|
+
import org.mockito.junit.jupiter.MockitoSettings
|
|
27
|
+
import org.mockito.kotlin.eq
|
|
28
|
+
import org.mockito.kotlin.whenever
|
|
29
|
+
import org.mockito.quality.Strictness
|
|
30
|
+
|
|
31
|
+
@Extensions(
|
|
32
|
+
ExtendWith(MockitoExtension::class),
|
|
33
|
+
ExtendWith(ForgeExtension::class)
|
|
34
|
+
)
|
|
35
|
+
@MockitoSettings(strictness = Strictness.LENIENT)
|
|
36
|
+
internal class TextViewUtilsTest {
|
|
37
|
+
private lateinit var testedUtils: TextViewUtils
|
|
38
|
+
|
|
39
|
+
@Mock
|
|
40
|
+
private lateinit var mockReactTextPropertiesResolver: ReactTextPropertiesResolver
|
|
41
|
+
|
|
42
|
+
@Mock
|
|
43
|
+
private lateinit var mockMappingContext: MappingContext
|
|
44
|
+
|
|
45
|
+
@Mock
|
|
46
|
+
private lateinit var mockTextView: TextView
|
|
47
|
+
|
|
48
|
+
@Mock
|
|
49
|
+
private lateinit var mockSystemInformation: SystemInformation
|
|
50
|
+
|
|
51
|
+
@Mock
|
|
52
|
+
private lateinit var mockResources: Resources
|
|
53
|
+
|
|
54
|
+
@Mock
|
|
55
|
+
private lateinit var mockDisplayMetrics: DisplayMetrics
|
|
56
|
+
|
|
57
|
+
@BeforeEach
|
|
58
|
+
fun `set up`(forge: Forge) {
|
|
59
|
+
whenever(mockResources.displayMetrics).thenReturn(mockDisplayMetrics)
|
|
60
|
+
whenever(mockTextView.resources).thenReturn(mockResources)
|
|
61
|
+
whenever(mockSystemInformation.screenDensity).thenReturn(0f)
|
|
62
|
+
whenever(mockMappingContext.systemInformation).thenReturn(mockSystemInformation)
|
|
63
|
+
whenever(mockTextView.text).thenReturn(forge.aString())
|
|
64
|
+
whenever(mockTextView.typeface).thenReturn(Typeface.SANS_SERIF)
|
|
65
|
+
|
|
66
|
+
testedUtils = TextViewUtils()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Test
|
|
70
|
+
fun `M return wireframe W map() { even if not TextWireframeType }`(
|
|
71
|
+
@Mock mockImageWireframe: MobileSegment.Wireframe.ImageWireframe
|
|
72
|
+
) {
|
|
73
|
+
// When
|
|
74
|
+
val result = testedUtils.mapTextViewToWireframes(
|
|
75
|
+
wireframes = listOf(mockImageWireframe),
|
|
76
|
+
view = mockTextView,
|
|
77
|
+
mappingContext = mockMappingContext,
|
|
78
|
+
reactTextPropertiesResolver = mockReactTextPropertiesResolver
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
// Then
|
|
82
|
+
assertThat(result).contains(mockImageWireframe)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Test
|
|
86
|
+
fun `M return textWireframe W map()`(
|
|
87
|
+
@Mock mockTextWireframe: MobileSegment.Wireframe.TextWireframe
|
|
88
|
+
) {
|
|
89
|
+
// Given
|
|
90
|
+
whenever(
|
|
91
|
+
mockReactTextPropertiesResolver.addReactNativeProperties(
|
|
92
|
+
originalWireframe = eq(mockTextWireframe),
|
|
93
|
+
view = eq(mockTextView),
|
|
94
|
+
pixelDensity = eq(0f)
|
|
95
|
+
)
|
|
96
|
+
).thenReturn(mockTextWireframe)
|
|
97
|
+
|
|
98
|
+
// When
|
|
99
|
+
val result = testedUtils.mapTextViewToWireframes(
|
|
100
|
+
wireframes = listOf(mockTextWireframe),
|
|
101
|
+
view = mockTextView,
|
|
102
|
+
mappingContext = mockMappingContext,
|
|
103
|
+
reactTextPropertiesResolver = mockReactTextPropertiesResolver
|
|
104
|
+
)[0] as MobileSegment.Wireframe.TextWireframe
|
|
105
|
+
|
|
106
|
+
// Then
|
|
107
|
+
assertThat(result).isEqualTo(mockTextWireframe)
|
|
108
|
+
}
|
|
109
|
+
}
|