Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(replay): Add tests for touch events #3924

Merged
merged 8 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package io.sentry.rnsentryandroidtester

import io.sentry.react.RNSentryReplayBreadcrumbConverter
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@RunWith(JUnit4::class)
class RNSentryReplayBreadcrumbConverterTest {

@Test
fun doesNotConvertNullPath() {
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(null)
assertEquals(null, actual)
}

@Test
fun doesNotConvertPathContainingNull() {
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(arrayListOf(arrayOfNulls<Any>(1)))
assertEquals(null, actual)
}

@Test
fun doesNotConvertPathWithValuesMissingNameAndLevel() {
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(arrayListOf(mapOf(
"element" to "element4",
"file" to "file4")))
assertEquals(null, actual)
}

@Test
fun doesConvertValidPathExample1() {
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(listOf(
mapOf("label" to "label0"),
mapOf("name" to "name1"),
mapOf("name" to "item2", "label" to "label2"),
mapOf("name" to "item3", "label" to "label3", "element" to "element3"),
mapOf("name" to "item4", "label" to "label4", "file" to "file4"),
mapOf("name" to "item5", "label" to "label5", "element" to "element5", "file" to "file5")))
assertEquals("label3(element3) > label2 > name1 > label0", actual)
}

@Test
fun doesConvertValidPathExample2() {
val actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(listOf(
mapOf("name" to "item2", "label" to "label2"),
mapOf("name" to "item3", "label" to "label3", "element" to "element3"),
mapOf("name" to "item4", "label" to "label4", "file" to "file4"),
mapOf("name" to "item5", "label" to "label5", "element" to "element5", "file" to "file5"),
mapOf("label" to "label6"),
mapOf("name" to "name7")))
assertEquals("label5(element5, file5) > label4(file4) > label3(element3) > label2", actual)
}
}
17 changes: 17 additions & 0 deletions RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
330F308C2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 330F308B2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m */; };
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */; };
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */; };
33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */; };
33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */; };
Expand All @@ -19,6 +20,9 @@
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>"; };
330F308B2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSentryBreadcrumbTests.m; sourceTree = "<group>"; };
330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryBreadcrumb.h; path = ../ios/RNSentryBreadcrumb.h; sourceTree = "<group>"; };
336084372C32E382008CC412 /* RNSentryCocoaTesterTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNSentryCocoaTesterTests-Bridging-Header.h"; sourceTree = "<group>"; };
336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNSentryReplayBreadcrumbConverterTests.swift; sourceTree = "<group>"; };
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayBreadcrumbConverter.h; path = ../ios/RNSentryReplayBreadcrumbConverter.h; sourceTree = "<group>"; };
3360898D29524164007C7730 /* RNSentryCocoaTesterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RNSentryCocoaTesterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
338739072A7D7D2800950DDD /* RNSentryTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSentryTests.h; sourceTree = "<group>"; };
33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryOnDrawReporter.h; path = ../ios/RNSentryOnDrawReporter.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -76,6 +80,7 @@
3360899029524164007C7730 /* RNSentryCocoaTesterTests */ = {
isa = PBXGroup;
children = (
336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */,
33F58ACF2977037D008F60EA /* RNSentryTests.mm */,
338739072A7D7D2800950DDD /* RNSentryTests.h */,
33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */,
Expand All @@ -84,13 +89,15 @@
33AFDFF22B8D15F600AAB120 /* RNSentryDependencyContainerTests.h */,
33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */,
330F308B2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m */,
336084372C32E382008CC412 /* RNSentryCocoaTesterTests-Bridging-Header.h */,
);
path = RNSentryCocoaTesterTests;
sourceTree = "<group>";
};
33AFE0122B8F319000AAB120 /* RNSentry */ = {
isa = PBXGroup;
children = (
3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */,
330F308D2C0F385A002A0D4E /* RNSentryBreadcrumb.h */,
33958C672BFCEF5A00AD1FB6 /* RNSentryOnDrawReporter.h */,
33AFE0132B8F31AF00AAB120 /* RNSentryDependencyContainer.h */,
Expand Down Expand Up @@ -134,10 +141,12 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1540;
LastUpgradeCheck = 1420;
TargetAttributes = {
3360898C29524164007C7730 = {
CreatedOnToolsVersion = 14.2;
LastSwiftMigration = 1540;
};
};
};
Expand Down Expand Up @@ -207,6 +216,7 @@
buildActionMask = 2147483647;
files = (
33AFDFF12B8D15E500AAB120 /* RNSentryDependencyContainerTests.m in Sources */,
336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */,
33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */,
33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */,
330F308C2C0F3840002A0D4E /* RNSentryBreadcrumbTests.m in Sources */,
Expand Down Expand Up @@ -333,6 +343,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = E2321E7CFA55AB617247098E /* Pods-RNSentryCocoaTesterTests.debug.xcconfig */;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
Expand Down Expand Up @@ -387,6 +398,9 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
Expand All @@ -395,6 +409,7 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 1482D5685A340AB93348A43D /* Pods-RNSentryCocoaTesterTests.release.xcconfig */;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
Expand Down Expand Up @@ -449,6 +464,8 @@
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_OBJC_BRIDGING_HEADER = "RNSentryCocoaTesterTests/RNSentryCocoaTesterTests-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "RNSentryReplayBreadcrumbConverter.h"
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import XCTest

final class RNSentryReplayBreadcrumbConverterTests: XCTestCase {

func testTouchMessageReturnsNilOnEmptyArray() throws {
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: [])
XCTAssertEqual(actual, nil);
}

func testTouchMessageReturnsNilOnNilArray() throws {
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: nil as [Any]?)
XCTAssertEqual(actual, nil);
}

func testTouchMessageReturnsNilOnMissingNameAndLevel() throws {
let testPath: [Any?] = [["element": "element4", "file": "file4"]]
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: testPath as [Any])
XCTAssertEqual(actual, nil);
}

func testTouchMessageReturnsMessageOnValidPathExample1() throws {
let testPath: [Any?] = [
["label": "label0"],
["name": "name1"],
["name": "item2", "label": "label2"],
["name": "item3", "label": "label3", "element": "element3"],
["name": "item4", "label": "label4", "file": "file4"],
["name": "item5", "label": "label5", "element": "element5", "file": "file5"],
]
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: testPath as [Any])
XCTAssertEqual(actual, "label3(element3) > label2 > name1 > label0");
}

func testTouchMessageReturnsMessageOnValidPathExample2() throws {
let testPath: [Any?] = [
["name": "item2", "label": "label2"],
["name": "item3", "label": "label3", "element": "element3"],
["name": "item4", "label": "label4", "file": "file4"],
["name": "item5", "label": "label5", "element": "element5", "file": "file5"],
["label": "label6"],
["name": "name7"],
]
let actual = RNSentryReplayBreadcrumbConverter.getTouchPathMessage(from: testPath as [Any])
XCTAssertEqual(actual, "label5(element5, file5) > label4(file4) > label3(element3) > label2");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
import io.sentry.rrweb.RRWebBreadcrumbEvent;
import io.sentry.rrweb.RRWebSpanEvent;

import java.util.ArrayList;
import java.util.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public final class RNSentryReplayBreadcrumbConverter extends DefaultReplayBreadcrumbConverter {
public RNSentryReplayBreadcrumbConverter() {
Expand Down Expand Up @@ -59,31 +59,9 @@ public RNSentryReplayBreadcrumbConverter() {
final RRWebBreadcrumbEvent rrWebBreadcrumb = new RRWebBreadcrumbEvent();

rrWebBreadcrumb.setCategory("ui.tap");
ArrayList path = (ArrayList) breadcrumb.getData("path");
if (path != null) {
StringBuilder message = new StringBuilder();
for (int i = Math.min(3, path.size()); i >= 0; i--) {
HashMap item = (HashMap) path.get(i);
message.append(item.get("name"));
if (item.containsKey("element") || item.containsKey("file")) {
message.append('(');
if (item.containsKey("element")) {
message.append(item.get("element"));
if (item.containsKey("file")) {
message.append(", ");
message.append(item.get("file"));
}
} else if (item.containsKey("file")) {
message.append(item.get("file"));
}
message.append(')');
}
if (i > 0) {
message.append(" > ");
}
}
rrWebBreadcrumb.setMessage(message.toString());
}

rrWebBreadcrumb.setMessage(RNSentryReplayBreadcrumbConverter
.getTouchPathMessage(breadcrumb.getData("path")));

rrWebBreadcrumb.setLevel(breadcrumb.getLevel());
rrWebBreadcrumb.setData(breadcrumb.getData());
Expand All @@ -93,6 +71,66 @@ public RNSentryReplayBreadcrumbConverter() {
return rrWebBreadcrumb;
}

@TestOnly
public static @Nullable String getTouchPathMessage(final @Nullable Object maybePath) {
if (!(maybePath instanceof List)) {
return null;
}

final @NotNull List path = (List) maybePath;
if (path.size() == 0) {
return null;
}

final @NotNull StringBuilder message = new StringBuilder();
for (int i = Math.min(3, path.size() - 1); i >= 0; i--) {
final @Nullable Object maybeItem = path.get(i);
if (!(maybeItem instanceof Map)) {
return null;
}

final @NotNull Map item = (Map) maybeItem;
final @Nullable Object maybeName = item.get("name");
final @Nullable Object maybeLabel = item.get("label");
boolean hasName = maybeName instanceof String;
boolean hasLabel = maybeLabel instanceof String;
if (!hasName && !hasLabel) {
return null; // This again should never be allowed in JS, but to be safe we check it here
}
if (hasLabel) {
message.append(maybeLabel);
} else { // hasName is true
message.append(maybeName);
}

final @Nullable Object maybeElement = item.get("element");
final @Nullable Object maybeFile = item.get("file");
boolean hasElement = maybeElement instanceof String;
boolean hasFile = maybeFile instanceof String;
if (hasElement && hasFile) {
message.append('(')
.append(maybeElement)
.append(", ")
.append(maybeFile)
.append(')');
} else if (hasElement) {
message.append('(')
.append(maybeElement)
.append(')');
} else if (hasFile) {
message.append('(')
.append(maybeFile)
.append(')');
}

if (i > 0) {
message.append(" > ");
}
}

return message.toString();
}

@TestOnly
public @Nullable RRWebEvent convertNetworkBreadcrumb(final @NotNull Breadcrumb breadcrumb) {
final Double startTimestamp = breadcrumb.getData("start_timestamp") instanceof Number
Expand Down
2 changes: 2 additions & 0 deletions ios/RNSentryReplayBreadcrumbConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@

- (instancetype _Nonnull)init;

+ (NSString* _Nullable) getTouchPathMessageFrom:(NSArray* _Nullable) path;

@end
#endif
Loading
Loading