Skip to content

Commit 5f3a5c8

Browse files
fix(replay): Add tests for touch events (#3924)
1 parent ac72abc commit 5f3a5c8

File tree

9 files changed

+324
-107
lines changed

9 files changed

+324
-107
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.sentry.rnsentryandroidtester
2+
3+
import io.sentry.react.RNSentryReplayBreadcrumbConverter
4+
import org.junit.Assert.assertEquals
5+
import org.junit.Test
6+
import org.junit.runner.RunWith
7+
import org.junit.runners.JUnit4
8+
9+
@RunWith(JUnit4::class)
10+
class RNSentryReplayBreadcrumbConverterTest {
11+
12+
@Test
13+
fun doesNotConvertNullPath() {
14+
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(null)
15+
assertEquals(null, actual)
16+
}
17+
18+
@Test
19+
fun doesNotConvertPathContainingNull() {
20+
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(arrayListOf(arrayOfNulls<Any>(1)))
21+
assertEquals(null, actual)
22+
}
23+
24+
@Test
25+
fun doesNotConvertPathWithValuesMissingNameAndLevel() {
26+
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(arrayListOf(mapOf(
27+
"element" to "element4",
28+
"file" to "file4")))
29+
assertEquals(null, actual)
30+
}
31+
32+
@Test
33+
fun doesConvertValidPathExample1() {
34+
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(listOf(
35+
mapOf("label" to "label0"),
36+
mapOf("name" to "name1"),
37+
mapOf("name" to "item2", "label" to "label2"),
38+
mapOf("name" to "item3", "label" to "label3", "element" to "element3"),
39+
mapOf("name" to "item4", "label" to "label4", "file" to "file4"),
40+
mapOf("name" to "item5", "label" to "label5", "element" to "element5", "file" to "file5")))
41+
assertEquals("label3(element3) > label2 > name1 > label0", actual)
42+
}
43+
44+
@Test
45+
fun doesConvertValidPathExample2() {
46+
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(listOf(
47+
mapOf("name" to "item2", "label" to "label2"),
48+
mapOf("name" to "item3", "label" to "label3", "element" to "element3"),
49+
mapOf("name" to "item4", "label" to "label4", "file" to "file4"),
50+
mapOf("name" to "item5", "label" to "label5", "element" to "element5", "file" to "file5"),
51+
mapOf("label" to "label6"),
52+
mapOf("name" to "name7")))
53+
assertEquals("label5(element5, file5) > label4(file4) > label3(element3) > label2", actual)
54+
}
55+
}

RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
330F308C2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 330F308B2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m */; };
11+
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */; };
1112
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */; };
1213
33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */; };
1314
33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */; };
@@ -19,6 +20,9 @@
1920
1482D5685A340AB93348A43D /* Pods-RNSentryCocoaTesterTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RNSentryCocoaTesterTests.release.xcconfig"; path = "Target Support Files/Pods-RNSentryCocoaTesterTests/Pods-RNSentryCocoaTesterTests.release.xcconfig"; sourceTree = "<group>"; };
2021
330F308B2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryBreadcrumbTests.m; sourceTree = "<group>"; };
2122
330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryBreadcrumb.h; path = ../ios/RNSentryBreadcrumb.h; sourceTree = "<group>"; };
23+
336084372C32E382008CC412 /* RNSentryCocoaTesterTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNSentryCocoaTesterTests-Bridging-Header.h"; sourceTree = "<group>"; };
24+
336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNSentryReplayBreadcrumbConverterTests.swift; sourceTree = "<group>"; };
25+
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayBreadcrumbConverter.h; path = ../ios/RNSentryReplayBreadcrumbConverter.h; sourceTree = "<group>"; };
2226
3360898D29524164007C7730 /* RNSentryCocoaTesterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RNSentryCocoaTesterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
2327
338739072A7D7D2800950DDD /* RNSentryTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSentryTests.h; sourceTree = "<group>"; };
2428
33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryOnDrawReporter.h; path = ../ios/RNSentryOnDrawReporter.h; sourceTree = "<group>"; };
@@ -76,6 +80,7 @@
7680
3360899029524164007C7730 /* RNSentryCocoaTesterTests */ = {
7781
isa = PBXGroup;
7882
children = (
83+
336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */,
7984
33F58ACF2977037D008F60EA /* RNSentryTests.mm */,
8085
338739072A7D7D2800950DDD /* RNSentryTests.h */,
8186
33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */,
@@ -84,13 +89,15 @@
8489
33AFDFF22B8D15F600AAB120 /* RNSentryDependencyContainerTests.h */,
8590
33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */,
8691
330F308B2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m */,
92+
336084372C32E382008CC412 /* RNSentryCocoaTesterTests-Bridging-Header.h */,
8793
);
8894
path = RNSentryCocoaTesterTests;
8995
sourceTree = "<group>";
9096
};
9197
33AFE0122B8F319000AAB120 /* RNSentry */ = {
9298
isa = PBXGroup;
9399
children = (
100+
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */,
94101
330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */,
95102
33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */,
96103
33AFE0132B8F31AF00AAB120 /* RNSentryDependencyContainer.h */,
@@ -134,10 +141,12 @@
134141
isa = PBXProject;
135142
attributes = {
136143
BuildIndependentTargetsInParallel = 1;
144+
LastSwiftUpdateCheck = 1540;
137145
LastUpgradeCheck = 1420;
138146
TargetAttributes = {
139147
3360898C29524164007C7730 = {
140148
CreatedOnToolsVersion = 14.2;
149+
LastSwiftMigration = 1540;
141150
};
142151
};
143152
};
@@ -207,6 +216,7 @@
207216
buildActionMask = 2147483647;
208217
files = (
209218
33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */,
219+
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */,
210220
33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */,
211221
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */,
212222
330F308C2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m in Sources */,
@@ -333,6 +343,7 @@
333343
isa = XCBuildConfiguration;
334344
baseConfigurationReference = E2321E7CFA55AB617247098E /* Pods-RNSentryCocoaTesterTests.debug.xcconfig */;
335345
buildSettings = {
346+
CLANG_ENABLE_MODULES = YES;
336347
CODE_SIGN_STYLE = Automatic;
337348
CURRENT_PROJECT_VERSION = 1;
338349
GENERATE_INFOPLIST_FILE = YES;
@@ -387,6 +398,9 @@
387398
SUPPORTS_MACCATALYST = NO;
388399
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
389400
SWIFT_EMIT_LOC_STRINGS = NO;
401+
SWIFT_OBJC_BRIDGING_HEADER = "RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h";
402+
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
403+
SWIFT_VERSION = 5.0;
390404
TARGETED_DEVICE_FAMILY = 1;
391405
};
392406
name = Debug;
@@ -395,6 +409,7 @@
395409
isa = XCBuildConfiguration;
396410
baseConfigurationReference = 1482D5685A340AB93348A43D /* Pods-RNSentryCocoaTesterTests.release.xcconfig */;
397411
buildSettings = {
412+
CLANG_ENABLE_MODULES = YES;
398413
CODE_SIGN_STYLE = Automatic;
399414
CURRENT_PROJECT_VERSION = 1;
400415
GENERATE_INFOPLIST_FILE = YES;
@@ -449,6 +464,8 @@
449464
SUPPORTS_MACCATALYST = NO;
450465
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
451466
SWIFT_EMIT_LOC_STRINGS = NO;
467+
SWIFT_OBJC_BRIDGING_HEADER = "RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h";
468+
SWIFT_VERSION = 5.0;
452469
TARGETED_DEVICE_FAMILY = 1;
453470
};
454471
name = Release;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//
2+
// Use this file to import your target's public headers that you would like to expose to Swift.
3+
//
4+
5+
#import "RNSentryReplayBreadcrumbConverter.h"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import XCTest
2+
3+
final class RNSentryReplayBreadcrumbConverterTests: XCTestCase {
4+
5+
func testTouchMessageReturnsNilOnEmptyArray() throws {
6+
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: [])
7+
XCTAssertEqual(actual, nil);
8+
}
9+
10+
func testTouchMessageReturnsNilOnNilArray() throws {
11+
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: nil as [Any]?)
12+
XCTAssertEqual(actual, nil);
13+
}
14+
15+
func testTouchMessageReturnsNilOnMissingNameAndLevel() throws {
16+
let testPath: [Any?] = [["element": "element4", "file": "file4"]]
17+
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: testPath as [Any])
18+
XCTAssertEqual(actual, nil);
19+
}
20+
21+
func testTouchMessageReturnsMessageOnValidPathExample1() throws {
22+
let testPath: [Any?] = [
23+
["label": "label0"],
24+
["name": "name1"],
25+
["name": "item2", "label": "label2"],
26+
["name": "item3", "label": "label3", "element": "element3"],
27+
["name": "item4", "label": "label4", "file": "file4"],
28+
["name": "item5", "label": "label5", "element": "element5", "file": "file5"],
29+
]
30+
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: testPath as [Any])
31+
XCTAssertEqual(actual, "label3(element3) > label2 > name1 > label0");
32+
}
33+
34+
func testTouchMessageReturnsMessageOnValidPathExample2() throws {
35+
let testPath: [Any?] = [
36+
["name": "item2", "label": "label2"],
37+
["name": "item3", "label": "label3", "element": "element3"],
38+
["name": "item4", "label": "label4", "file": "file4"],
39+
["name": "item5", "label": "label5", "element": "element5", "file": "file5"],
40+
["label": "label6"],
41+
["name": "name7"],
42+
]
43+
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: testPath as [Any])
44+
XCTAssertEqual(actual, "label5(element5, file5) > label4(file4) > label3(element3) > label2");
45+
}
46+
47+
}

android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import io.sentry.rrweb.RRWebBreadcrumbEvent;
77
import io.sentry.rrweb.RRWebSpanEvent;
88

9-
import java.util.ArrayList;
109
import java.util.HashMap;
1110
import org.jetbrains.annotations.NotNull;
1211
import org.jetbrains.annotations.Nullable;
1312
import org.jetbrains.annotations.TestOnly;
1413

15-
import java.util.HashMap;
14+
import java.util.List;
15+
import java.util.Map;
1616

1717
public final class RNSentryReplayBreadcrumbConverter extends DefaultReplayBreadcrumbConverter {
1818
public RNSentryReplayBreadcrumbConverter() {
@@ -59,31 +59,9 @@ public RNSentryReplayBreadcrumbConverter() {
5959
final RRWebBreadcrumbEvent rrWebBreadcrumb = new RRWebBreadcrumbEvent();
6060

6161
rrWebBreadcrumb.setCategory("ui.tap");
62-
ArrayList path = (ArrayList) breadcrumb.getData("path");
63-
if (path != null) {
64-
StringBuilder message = new StringBuilder();
65-
for (int i = Math.min(3, path.size()); i >= 0; i--) {
66-
HashMap item = (HashMap) path.get(i);
67-
message.append(item.get("name"));
68-
if (item.containsKey("element") || item.containsKey("file")) {
69-
message.append('(');
70-
if (item.containsKey("element")) {
71-
message.append(item.get("element"));
72-
if (item.containsKey("file")) {
73-
message.append(", ");
74-
message.append(item.get("file"));
75-
}
76-
} else if (item.containsKey("file")) {
77-
message.append(item.get("file"));
78-
}
79-
message.append(')');
80-
}
81-
if (i > 0) {
82-
message.append(" > ");
83-
}
84-
}
85-
rrWebBreadcrumb.setMessage(message.toString());
86-
}
62+
63+
rrWebBreadcrumb.setMessage(RNSentryReplayBreadcrumbConverter
64+
.getTouchPathMessage(breadcrumb.getData("path")));
8765

8866
rrWebBreadcrumb.setLevel(breadcrumb.getLevel());
8967
rrWebBreadcrumb.setData(breadcrumb.getData());
@@ -93,6 +71,66 @@ public RNSentryReplayBreadcrumbConverter() {
9371
return rrWebBreadcrumb;
9472
}
9573

74+
@TestOnly
75+
public static @Nullable String getTouchPathMessage(final @Nullable Object maybePath) {
76+
if (!(maybePath instanceof List)) {
77+
return null;
78+
}
79+
80+
final @NotNull List path = (List) maybePath;
81+
if (path.size() == 0) {
82+
return null;
83+
}
84+
85+
final @NotNull StringBuilder message = new StringBuilder();
86+
for (int i = Math.min(3, path.size() - 1); i >= 0; i--) {
87+
final @Nullable Object maybeItem = path.get(i);
88+
if (!(maybeItem instanceof Map)) {
89+
return null;
90+
}
91+
92+
final @NotNull Map item = (Map) maybeItem;
93+
final @Nullable Object maybeName = item.get("name");
94+
final @Nullable Object maybeLabel = item.get("label");
95+
boolean hasName = maybeName instanceof String;
96+
boolean hasLabel = maybeLabel instanceof String;
97+
if (!hasName && !hasLabel) {
98+
return null; // This again should never be allowed in JS, but to be safe we check it here
99+
}
100+
if (hasLabel) {
101+
message.append(maybeLabel);
102+
} else { // hasName is true
103+
message.append(maybeName);
104+
}
105+
106+
final @Nullable Object maybeElement = item.get("element");
107+
final @Nullable Object maybeFile = item.get("file");
108+
boolean hasElement = maybeElement instanceof String;
109+
boolean hasFile = maybeFile instanceof String;
110+
if (hasElement && hasFile) {
111+
message.append('(')
112+
.append(maybeElement)
113+
.append(", ")
114+
.append(maybeFile)
115+
.append(')');
116+
} else if (hasElement) {
117+
message.append('(')
118+
.append(maybeElement)
119+
.append(')');
120+
} else if (hasFile) {
121+
message.append('(')
122+
.append(maybeFile)
123+
.append(')');
124+
}
125+
126+
if (i > 0) {
127+
message.append(" > ");
128+
}
129+
}
130+
131+
return message.toString();
132+
}
133+
96134
@TestOnly
97135
public @Nullable RRWebEvent convertNetworkBreadcrumb(final @NotNull Breadcrumb breadcrumb) {
98136
final Double startTimestamp = breadcrumb.getData("start_timestamp") instanceof Number

ios/RNSentryReplayBreadcrumbConverter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88

99
- (instancetype _Nonnull)init;
1010

11+
+ (NSString* _Nullable) getTouchPathMessageFrom:(NSArray* _Nullable) path;
12+
1113
@end
1214
#endif

0 commit comments

Comments
 (0)