diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index f0454305f1c..622f098cc94 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -769,6 +769,7 @@ D43B26DA2D70A612007747FD /* SentrySpanDataKey.m in Sources */ = {isa = PBXBuildFile; fileRef = D43B26D92D70A60E007747FD /* SentrySpanDataKey.m */; }; D43C1BE82E8FB85400CD5D67 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D43C1BE72E8FB85400CD5D67 /* SnapshotTesting */; }; D4411DD52E02B74900EA4987 /* ArrayAccessesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4411DD42E02B74100EA4987 /* ArrayAccessesTests.swift */; }; + D44311312EB22812006CABE4 /* SentryUIRedactBuilderTests+ReactNative.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */; }; D44B16722DE464AD006DBDB3 /* TestDispatchFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44B16712DE464A9006DBDB3 /* TestDispatchFactoryTests.swift */; }; D451ED5D2D92ECD200C9BEA8 /* SentryOnDemandReplayError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D451ED5C2D92ECD200C9BEA8 /* SentryOnDemandReplayError.swift */; }; D451ED5F2D92ECDE00C9BEA8 /* SentryReplayFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D451ED5E2D92ECDE00C9BEA8 /* SentryReplayFrame.swift */; }; @@ -2180,6 +2181,7 @@ D4AF00202D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSFileManagerSwizzling.m; sourceTree = ""; }; D4AF00222D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSFileManagerSwizzling.h; path = include/SentryNSFileManagerSwizzling.h; sourceTree = ""; }; D4AF00242D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSFileManagerSwizzlingTests.m; sourceTree = ""; }; + D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryUIRedactBuilderTests+ReactNative.swift"; sourceTree = ""; }; D4AF7D292E940492004F0F59 /* SentryUIRedactBuilderTests+Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryUIRedactBuilderTests+Common.swift"; sourceTree = ""; }; D4B0DC7E2DA9257200DE61B6 /* SentryRenderVideoResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRenderVideoResult.swift; sourceTree = ""; }; D4BCA0C22DA93C25009E49AB /* SentrySessionReplayIntegration+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentrySessionReplayIntegration+Test.h"; sourceTree = ""; }; @@ -4241,6 +4243,7 @@ D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */, D8F67AF22BE10F7600C9197B /* SentryUIRedactBuilderTests.swift */, D4AF7D292E940492004F0F59 /* SentryUIRedactBuilderTests+Common.swift */, + D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */, D45E2D762E003EBF0072A6B7 /* TestRedactOptions.swift */, ); path = ViewCapture; @@ -4348,13 +4351,6 @@ path = Screenshot; sourceTree = ""; }; - D4AF802E2E965188004F0F59 /* __Snapshots__ */ = { - isa = PBXGroup; - children = ( - ); - path = __Snapshots__; - sourceTree = ""; - }; D4A0C22A2E9E3CE100791353 /* InfoPlist */ = { isa = PBXGroup; children = ( @@ -4367,6 +4363,13 @@ path = InfoPlist; sourceTree = ""; }; + D4AF802E2E965188004F0F59 /* __Snapshots__ */ = { + isa = PBXGroup; + children = ( + ); + path = __Snapshots__; + sourceTree = ""; + }; D4CBA2522DE06D1600581618 /* SentryTestUtilsTests */ = { isa = PBXGroup; children = ( @@ -6186,6 +6189,7 @@ 7BC6EBF4255C044A0059822A /* SentryEventTests.swift in Sources */, 63FE721920DA66EC00CDBAE8 /* SentryCrashReportStore_Tests.m in Sources */, 7B6D98EB24C6E84F005502FA /* SentryCrashInstallationReporterTests.swift in Sources */, + D44311312EB22812006CABE4 /* SentryUIRedactBuilderTests+ReactNative.swift in Sources */, 7BA61EA625F21E660008CAA2 /* SentrySDKLogTests.swift in Sources */, 62CFD9A92C99741100834E1B /* SentryInvalidJSONStringTests.swift in Sources */, 7BB42EF124F3B7B700D7B39A /* SentrySession+Equality.m in Sources */, diff --git a/Tests/SentryTests/ViewCapture/SentryUIRedactBuilderTests+ReactNative.swift b/Tests/SentryTests/ViewCapture/SentryUIRedactBuilderTests+ReactNative.swift new file mode 100644 index 00000000000..a0d252ea995 --- /dev/null +++ b/Tests/SentryTests/ViewCapture/SentryUIRedactBuilderTests+ReactNative.swift @@ -0,0 +1,244 @@ +#if os(iOS) && !targetEnvironment(macCatalyst) +import AVKit +import Foundation +import PDFKit +import SafariServices +@_spi(Private) @testable import Sentry +import SentryTestUtils +import SnapshotTesting +import SwiftUI +import UIKit +import WebKit +import XCTest + +/* + * Mocked RCTTextView to test the redaction of text from React Native apps. + */ +@objc(RCTTextView) +private class RCTTextView: UIView { +} + +/* + * Mocked RCTParagraphComponentView to test the redaction of text from React Native apps. + */ +@objc(RCTParagraphComponentView) +private class RCTParagraphComponentView: UIView { +} + +/* + * Mocked RCTImageView to test the redaction of images from React Native apps. + */ +@objc(RCTImageView) +private class RCTImageView: UIView { +} + +/// See `SentryUIRedactBuilderTests.swift` for more information on how to print the internal view hierarchy of a view. +class SentryUIRedactBuilderTests_ReactNative: SentryUIRedactBuilderTests { // swiftlint:disable:this type_name + private func getSut(maskAllText: Bool, maskAllImages: Bool) -> SentryUIRedactBuilder { + return SentryUIRedactBuilder(options: TestRedactOptions( + maskAllText: maskAllText, + maskAllImages: maskAllImages + )) + } + + // MARK: - RCTTextView Redaction + + private func setupRCTTextViewFixture() -> UIView { + let rootView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + + let textView = RCTTextView(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) + rootView.addSubview(textView) + + return rootView + + // View Hierarchy: + // --------------- + // > + // | > + } + + func testRedact_withRCTTextView_withMaskAllTextEnabled_shouldRedactView() throws { + // -- Arrange -- + let rootView = setupRCTTextViewFixture() + + // -- Act -- + let sut = getSut(maskAllText: true, maskAllImages: true) + let result = sut.redactRegionsFor(view: rootView) + let masked = createMaskedScreenshot(view: rootView, regions: result) + + // -- Assert -- + assertSnapshot(of: masked, as: .image) + + let region = try XCTUnwrap(result.element(at: 0)) + // The text color of UITextView is not used for redaction + XCTAssertNil(region.color) + XCTAssertEqual(region.size, CGSize(width: 40, height: 40)) + XCTAssertEqual(region.type, .redact) + XCTAssertEqual(region.transform, CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: 20, ty: 20)) + + // Assert that there are no other regions + XCTAssertEqual(result.count, 1) + } + + func testRedact_withRCTTextView_withMaskAllTextDisabled_shouldNotRedactView() { + // -- Arrange -- + let rootView = setupRCTTextViewFixture() + + // -- Act -- + let sut = getSut(maskAllText: false, maskAllImages: true) + let result = sut.redactRegionsFor(view: rootView) + let masked = createMaskedScreenshot(view: rootView, regions: result) + + // -- Assert -- + assertSnapshot(of: masked, as: .image) + XCTAssertEqual(result.count, 0) + } + + func testRedact_withRCTTextView_withMaskAllImagesDisabled_shouldRedactView() { + // -- Arrange -- + let rootView = setupRCTTextViewFixture() + + // -- Act -- + let sut = getSut(maskAllText: true, maskAllImages: false) + let result = sut.redactRegionsFor(view: rootView) + let masked = createMaskedScreenshot(view: rootView, regions: result) + + // -- Assert -- + assertSnapshot(of: masked, as: .image) + XCTAssertEqual(result.count, 1) + } + + // MARK: - RCTParagraphComponentView Redaction + + private func setupRCTParagraphComponentFixture() -> UIView { + let rootView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + + let textView = RCTParagraphComponentView(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) + rootView.addSubview(textView) + + return rootView + + // View Hierarchy: + // --------------- + // > + // | > + } + + func testRedact_withRCTParagraphComponent_withMaskAllTextEnabled_shouldRedactView() throws { + // -- Arrange -- + let rootView = setupRCTParagraphComponentFixture() + + // -- Act -- + let sut = getSut(maskAllText: true, maskAllImages: true) + let result = sut.redactRegionsFor(view: rootView) + let masked = createMaskedScreenshot(view: rootView, regions: result) + + // -- Assert -- + assertSnapshot(of: masked, as: .image) + + let region = try XCTUnwrap(result.element(at: 0)) + // The text color of UITextView is not used for redaction + XCTAssertNil(region.color) + XCTAssertEqual(region.size, CGSize(width: 40, height: 40)) + XCTAssertEqual(region.type, .redact) + XCTAssertEqual(region.transform, CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: 20, ty: 20)) + + // Assert that there are no other regions + XCTAssertEqual(result.count, 1) + } + + func testRedact_withRCTParagraphComponent_withMaskAllTextDisabled_shouldNotRedactView() { + // -- Arrange -- + let rootView = setupRCTParagraphComponentFixture() + + // -- Act -- + let sut = getSut(maskAllText: false, maskAllImages: true) + let result = sut.redactRegionsFor(view: rootView) + let masked = createMaskedScreenshot(view: rootView, regions: result) + + // -- Assert -- + assertSnapshot(of: masked, as: .image) + XCTAssertEqual(result.count, 0) + } + + func testRedact_withRCTParagraphComponent_withMaskAllImagesDisabled_shouldRedactView() { + // -- Arrange -- + let rootView = setupRCTParagraphComponentFixture() + + // -- Act -- + let sut = getSut(maskAllText: true, maskAllImages: false) + let result = sut.redactRegionsFor(view: rootView) + let masked = createMaskedScreenshot(view: rootView, regions: result) + + // -- Assert -- + assertSnapshot(of: masked, as: .image) + XCTAssertEqual(result.count, 1) + } + + // - MARK: - RCTImageView Redaction + + private func setupRCTImageViewFixture() -> UIView { + let rootView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + let imageView = RCTImageView(frame: CGRect(x: 20, y: 20, width: 40, height: 40)) + rootView.addSubview(imageView) + + // View Hierarchy: + // --------------- + // > + // | > + return rootView + } + + func testRedact_withRCTImageView_withMaskAllImagesEnabled_shouldRedactView() throws { + // -- Arrange -- + let rootView = setupRCTImageViewFixture() + + // -- Act -- + let sut = getSut(maskAllText: true, maskAllImages: true) + let result = sut.redactRegionsFor(view: rootView) + let masked = createMaskedScreenshot(view: rootView, regions: result) + + // -- Assert -- + assertSnapshot(of: masked, as: .image) + + let region = try XCTUnwrap(result.element(at: 0)) + // The text color of UITextView is not used for redaction + XCTAssertNil(region.color) + XCTAssertEqual(region.size, CGSize(width: 40, height: 40)) + XCTAssertEqual(region.type, .redact) + XCTAssertEqual(region.transform, CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: 20, ty: 20)) + + // Assert that there are no other regions + XCTAssertEqual(result.count, 1) + } + + func testRedact_withRCTImageView_withMaskAllImagesDisabled_shouldNotRedactView() { + // -- Arrange -- + let rootView = setupRCTImageViewFixture() + + // -- Act -- + let sut = getSut(maskAllText: true, maskAllImages: false) + let result = sut.redactRegionsFor(view: rootView) + let masked = createMaskedScreenshot(view: rootView, regions: result) + + // -- Assert -- + assertSnapshot(of: masked, as: .image) + XCTAssertEqual(result.count, 0) + } + + func testRedact_withRCTImageView_withMaskAllTextDisabled_shouldRedactView() { + // -- Arrange -- + let rootView = setupRCTImageViewFixture() + + // -- Act -- + let sut = getSut(maskAllText: false, maskAllImages: true) + let result = sut.redactRegionsFor(view: rootView) + let masked = createMaskedScreenshot(view: rootView, regions: result) + + // -- Assert -- + assertSnapshot(of: masked, as: .image) + XCTAssertEqual(result.count, 1) + } +} + +#endif // os(iOS) && !targetEnvironment(macCatalyst) diff --git a/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTImageView_withMaskAllImagesDisabled_shouldNotRedactView.1.png b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTImageView_withMaskAllImagesDisabled_shouldNotRedactView.1.png new file mode 100644 index 00000000000..01f6de9f94d Binary files /dev/null and b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTImageView_withMaskAllImagesDisabled_shouldNotRedactView.1.png differ diff --git a/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTImageView_withMaskAllImagesEnabled_shouldRedactView.1.png b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTImageView_withMaskAllImagesEnabled_shouldRedactView.1.png new file mode 100644 index 00000000000..b2031108477 Binary files /dev/null and b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTImageView_withMaskAllImagesEnabled_shouldRedactView.1.png differ diff --git a/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTImageView_withMaskAllTextDisabled_shouldRedactView.1.png b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTImageView_withMaskAllTextDisabled_shouldRedactView.1.png new file mode 100644 index 00000000000..b2031108477 Binary files /dev/null and b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTImageView_withMaskAllTextDisabled_shouldRedactView.1.png differ diff --git a/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTParagraphComponent_withMaskAllImagesDisabled_shouldRedactView.1.png b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTParagraphComponent_withMaskAllImagesDisabled_shouldRedactView.1.png new file mode 100644 index 00000000000..b2031108477 Binary files /dev/null and b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTParagraphComponent_withMaskAllImagesDisabled_shouldRedactView.1.png differ diff --git a/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTParagraphComponent_withMaskAllTextDisabled_shouldNotRedactView.1.png b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTParagraphComponent_withMaskAllTextDisabled_shouldNotRedactView.1.png new file mode 100644 index 00000000000..01f6de9f94d Binary files /dev/null and b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTParagraphComponent_withMaskAllTextDisabled_shouldNotRedactView.1.png differ diff --git a/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTParagraphComponent_withMaskAllTextEnabled_shouldRedactView.1.png b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTParagraphComponent_withMaskAllTextEnabled_shouldRedactView.1.png new file mode 100644 index 00000000000..b2031108477 Binary files /dev/null and b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTParagraphComponent_withMaskAllTextEnabled_shouldRedactView.1.png differ diff --git a/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTTextView_withMaskAllImagesDisabled_shouldRedactView.1.png b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTTextView_withMaskAllImagesDisabled_shouldRedactView.1.png new file mode 100644 index 00000000000..b2031108477 Binary files /dev/null and b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTTextView_withMaskAllImagesDisabled_shouldRedactView.1.png differ diff --git a/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTTextView_withMaskAllTextDisabled_shouldNotRedactView.1.png b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTTextView_withMaskAllTextDisabled_shouldNotRedactView.1.png new file mode 100644 index 00000000000..01f6de9f94d Binary files /dev/null and b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTTextView_withMaskAllTextDisabled_shouldNotRedactView.1.png differ diff --git a/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTTextView_withMaskAllTextEnabled_shouldRedactView.1.png b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTTextView_withMaskAllTextEnabled_shouldRedactView.1.png new file mode 100644 index 00000000000..b2031108477 Binary files /dev/null and b/Tests/SentryTests/ViewCapture/__Snapshots__/SentryUIRedactBuilderTests+ReactNative/testRedact_withRCTTextView_withMaskAllTextEnabled_shouldRedactView.1.png differ