Skip to content

Commit 0b17145

Browse files
committed
test(session-replay): Add masking tests for React Native views
1 parent e00e236 commit 0b17145

11 files changed

+248
-0
lines changed

Sentry.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,7 @@
814814
D4AF00212D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m in Sources */ = {isa = PBXBuildFile; fileRef = D4AF00202D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m */; };
815815
D4AF00232D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h in Headers */ = {isa = PBXBuildFile; fileRef = D4AF00222D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h */; };
816816
D4AF00252D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D4AF00242D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m */; };
817+
D4AF7D222E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */; };
817818
D4AF7D2A2E940493004F0F59 /* SentryUIRedactBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AF7D292E940492004F0F59 /* SentryUIRedactBuilderTests.swift */; };
818819
D4B0DC7F2DA9257A00DE61B6 /* SentryRenderVideoResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4B0DC7E2DA9257200DE61B6 /* SentryRenderVideoResult.swift */; };
819820
D4B339F92EA7823000359F3A /* SentryTestUtilsDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D84DAD4D2B17428D003CF120 /* SentryTestUtilsDynamic.framework */; };
@@ -2180,6 +2181,7 @@
21802181
D4AF00202D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSFileManagerSwizzling.m; sourceTree = "<group>"; };
21812182
D4AF00222D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSFileManagerSwizzling.h; path = include/SentryNSFileManagerSwizzling.h; sourceTree = "<group>"; };
21822183
D4AF00242D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSFileManagerSwizzlingTests.m; sourceTree = "<group>"; };
2184+
D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryUIRedactBuilderTests+ReactNative.swift"; sourceTree = "<group>"; };
21832185
D4AF7D292E940492004F0F59 /* SentryUIRedactBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUIRedactBuilderTests.swift; sourceTree = "<group>"; };
21842186
D4B0DC7E2DA9257200DE61B6 /* SentryRenderVideoResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRenderVideoResult.swift; sourceTree = "<group>"; };
21852187
D4BCA0C22DA93C25009E49AB /* SentrySessionReplayIntegration+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentrySessionReplayIntegration+Test.h"; sourceTree = "<group>"; };
@@ -4241,6 +4243,7 @@
42414243
D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */,
42424244
D4AF7D292E940492004F0F59 /* SentryUIRedactBuilderTests.swift */,
42434245
D8F67AF22BE10F7600C9197B /* SentryUIRedactBuilderTests+Common.swift */,
4246+
D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */,
42444247
D45E2D762E003EBF0072A6B7 /* TestRedactOptions.swift */,
42454248
);
42464249
path = ViewCapture;
@@ -6414,6 +6417,7 @@
64146417
7B68D93625FF5F1A0082D139 /* SentryAppState+Equality.m in Sources */,
64156418
7B5CAF7E27F5AD3500ED0DB6 /* TestNSURLRequestBuilder.m in Sources */,
64166419
D467125E2DCCFF2500D4074A /* SentryReplayOptionsObjcTests.m in Sources */,
6420+
D4AF7D222E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift in Sources */,
64176421
7BF69E072987D1FE002EBCA4 /* SentryCrashDoctorTests.swift in Sources */,
64186422
7B4F22DC294089530067EA17 /* FormatHexAddress.swift in Sources */,
64196423
8EAC7FF8265C8910005B44E5 /* SentryTracerTests.swift in Sources */,
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#if os(iOS) && !targetEnvironment(macCatalyst)
2+
import AVKit
3+
import Foundation
4+
import PDFKit
5+
import SafariServices
6+
@_spi(Private) @testable import Sentry
7+
import SentryTestUtils
8+
import SnapshotTesting
9+
import SwiftUI
10+
import UIKit
11+
import WebKit
12+
import XCTest
13+
14+
/*
15+
* Mocked RCTTextView to test the redaction of text from React Native apps.
16+
*/
17+
@objc(RCTTextView)
18+
private class RCTTextView: UIView {
19+
}
20+
21+
/*
22+
* Mocked RCTParagraphComponentView to test the redaction of text from React Native apps.
23+
*/
24+
@objc(RCTParagraphComponentView)
25+
private class RCTParagraphComponentView: UIView {
26+
}
27+
28+
/*
29+
* Mocked RCTImageView to test the redaction of images from React Native apps.
30+
*/
31+
@objc(RCTImageView)
32+
private class RCTImageView: UIView {
33+
}
34+
35+
/// See `SentryUIRedactBuilderTests.swift` for more information on how to print the internal view hierarchy of a view.
36+
class SentryUIRedactBuilderTests_ReactNative: SentryUIRedactBuilderTests { // swiftlint:disable:this type_name
37+
private func getSut(maskAllText: Bool, maskAllImages: Bool) -> SentryUIRedactBuilder {
38+
return SentryUIRedactBuilder(options: TestRedactOptions(
39+
maskAllText: maskAllText,
40+
maskAllImages: maskAllImages
41+
))
42+
}
43+
44+
// MARK: - RCTTextView Redaction
45+
46+
private func setupRCTTextViewFixture() -> UIView {
47+
let rootView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
48+
49+
let textView = RCTTextView(frame: CGRect(x: 20, y: 20, width: 40, height: 40))
50+
rootView.addSubview(textView)
51+
52+
return rootView
53+
54+
// View Hierarchy:
55+
// ---------------
56+
// <UIView: 0x10594ea10; frame = (0 0; 100 100); layer = <CALayer: 0x600000ce53b0>>
57+
// | <RCTTextView: 0x105951d60; frame = (20 20; 40 40); layer = <CALayer: 0x600000ce6790>>
58+
}
59+
60+
func testRedact_withRCTTextView_withMaskAllTextEnabled_shouldRedactView() throws {
61+
// -- Arrange --
62+
let rootView = setupRCTTextViewFixture()
63+
64+
// -- Act --
65+
let sut = getSut(maskAllText: true, maskAllImages: true)
66+
let result = sut.redactRegionsFor(view: rootView)
67+
let masked = createMaskedScreenshot(view: rootView, regions: result)
68+
69+
// -- Assert --
70+
assertSnapshot(of: masked, as: .image)
71+
72+
let region = try XCTUnwrap(result.element(at: 0))
73+
// The text color of UITextView is not used for redaction
74+
XCTAssertNil(region.color)
75+
XCTAssertEqual(region.size, CGSize(width: 40, height: 40))
76+
XCTAssertEqual(region.type, .redact)
77+
XCTAssertEqual(region.transform, CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: 20, ty: 20))
78+
79+
// Assert that there are no other regions
80+
XCTAssertEqual(result.count, 1)
81+
}
82+
83+
func testRedact_withRCTTextView_withMaskAllTextDisabled_shouldNotRedactView() {
84+
// -- Arrange --
85+
let rootView = setupRCTTextViewFixture()
86+
87+
// -- Act --
88+
let sut = getSut(maskAllText: false, maskAllImages: true)
89+
let result = sut.redactRegionsFor(view: rootView)
90+
let masked = createMaskedScreenshot(view: rootView, regions: result)
91+
92+
// -- Assert --
93+
assertSnapshot(of: masked, as: .image)
94+
XCTAssertEqual(result.count, 0)
95+
}
96+
97+
func testRedact_withRCTTextView_withMaskAllImagesDisabled_shouldRedactView() {
98+
// -- Arrange --
99+
let rootView = setupRCTTextViewFixture()
100+
101+
// -- Act --
102+
let sut = getSut(maskAllText: true, maskAllImages: false)
103+
let result = sut.redactRegionsFor(view: rootView)
104+
let masked = createMaskedScreenshot(view: rootView, regions: result)
105+
106+
// -- Assert --
107+
assertSnapshot(of: masked, as: .image)
108+
XCTAssertEqual(result.count, 1)
109+
}
110+
111+
// MARK: - RCTParagraphComponentView Redaction
112+
113+
private func setupRCTParagraphComponentFixture() -> UIView {
114+
let rootView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
115+
116+
let textView = RCTParagraphComponentView(frame: CGRect(x: 20, y: 20, width: 40, height: 40))
117+
rootView.addSubview(textView)
118+
119+
return rootView
120+
121+
// View Hierarchy:
122+
// ---------------
123+
// <UIView: 0x11a943f30; frame = (0 0; 100 100); layer = <CALayer: 0x600000cda3d0>>
124+
// | <RCTParagraphComponentView: 0x106350670; frame = (20 20; 40 40); layer = <CALayer: 0x600000cdaa60>>
125+
}
126+
127+
func testRedact_withRCTParagraphComponent_withMaskAllTextEnabled_shouldRedactView() throws {
128+
// -- Arrange --
129+
let rootView = setupRCTParagraphComponentFixture()
130+
131+
// -- Act --
132+
let sut = getSut(maskAllText: true, maskAllImages: true)
133+
let result = sut.redactRegionsFor(view: rootView)
134+
let masked = createMaskedScreenshot(view: rootView, regions: result)
135+
136+
// -- Assert --
137+
assertSnapshot(of: masked, as: .image)
138+
139+
let region = try XCTUnwrap(result.element(at: 0))
140+
// The text color of UITextView is not used for redaction
141+
XCTAssertNil(region.color)
142+
XCTAssertEqual(region.size, CGSize(width: 40, height: 40))
143+
XCTAssertEqual(region.type, .redact)
144+
XCTAssertEqual(region.transform, CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: 20, ty: 20))
145+
146+
// Assert that there are no other regions
147+
XCTAssertEqual(result.count, 1)
148+
}
149+
150+
func testRedact_withRCTParagraphComponent_withMaskAllTextDisabled_shouldNotRedactView() {
151+
// -- Arrange --
152+
let rootView = setupRCTParagraphComponentFixture()
153+
154+
// -- Act --
155+
let sut = getSut(maskAllText: false, maskAllImages: true)
156+
let result = sut.redactRegionsFor(view: rootView)
157+
let masked = createMaskedScreenshot(view: rootView, regions: result)
158+
159+
// -- Assert --
160+
assertSnapshot(of: masked, as: .image)
161+
XCTAssertEqual(result.count, 0)
162+
}
163+
164+
func testRedact_withRCTParagraphComponent_withMaskAllImagesDisabled_shouldRedactView() {
165+
// -- Arrange --
166+
let rootView = setupRCTParagraphComponentFixture()
167+
168+
// -- Act --
169+
let sut = getSut(maskAllText: true, maskAllImages: false)
170+
let result = sut.redactRegionsFor(view: rootView)
171+
let masked = createMaskedScreenshot(view: rootView, regions: result)
172+
173+
// -- Assert --
174+
assertSnapshot(of: masked, as: .image)
175+
XCTAssertEqual(result.count, 1)
176+
}
177+
178+
// - MARK: - RCTImageView Redaction
179+
180+
private func setupRCTImageViewFixture() -> UIView {
181+
let rootView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
182+
let imageView = RCTImageView(frame: CGRect(x: 20, y: 20, width: 40, height: 40))
183+
rootView.addSubview(imageView)
184+
185+
// View Hierarchy:
186+
// ---------------
187+
// <UIView: 0x10584f470; frame = (0 0; 100 100); layer = <CALayer: 0x600000ce8fc0>>
188+
// | <RCTImageView: 0x10585e6a0; frame = (20 20; 40 40); layer = <CALayer: 0x600000cea130>>
189+
return rootView
190+
}
191+
192+
func testRedact_withRCTImageView_withMaskAllImagesEnabled_shouldRedactView() throws {
193+
// -- Arrange --
194+
let rootView = setupRCTImageViewFixture()
195+
196+
// -- Act --
197+
let sut = getSut(maskAllText: true, maskAllImages: true)
198+
let result = sut.redactRegionsFor(view: rootView)
199+
let masked = createMaskedScreenshot(view: rootView, regions: result)
200+
201+
// -- Assert --
202+
assertSnapshot(of: masked, as: .image)
203+
204+
let region = try XCTUnwrap(result.element(at: 0))
205+
// The text color of UITextView is not used for redaction
206+
XCTAssertNil(region.color)
207+
XCTAssertEqual(region.size, CGSize(width: 40, height: 40))
208+
XCTAssertEqual(region.type, .redact)
209+
XCTAssertEqual(region.transform, CGAffineTransform(a: 1, b: 0, c: 0, d: 1, tx: 20, ty: 20))
210+
211+
// Assert that there are no other regions
212+
XCTAssertEqual(result.count, 1)
213+
}
214+
215+
func testRedact_withRCTImageView_withMaskAllImagesDisabled_shouldNotRedactView() {
216+
// -- Arrange --
217+
let rootView = setupRCTImageViewFixture()
218+
219+
// -- Act --
220+
let sut = getSut(maskAllText: true, maskAllImages: false)
221+
let result = sut.redactRegionsFor(view: rootView)
222+
let masked = createMaskedScreenshot(view: rootView, regions: result)
223+
224+
// -- Assert --
225+
assertSnapshot(of: masked, as: .image)
226+
XCTAssertEqual(result.count, 0)
227+
}
228+
229+
func testRedact_withRCTImageView_withMaskAllTextDisabled_shouldRedactView() {
230+
// -- Arrange --
231+
let rootView = setupRCTImageViewFixture()
232+
233+
// -- Act --
234+
let sut = getSut(maskAllText: false, maskAllImages: true)
235+
let result = sut.redactRegionsFor(view: rootView)
236+
let masked = createMaskedScreenshot(view: rootView, regions: result)
237+
238+
// -- Assert --
239+
assertSnapshot(of: masked, as: .image)
240+
XCTAssertEqual(result.count, 1)
241+
}
242+
}
243+
244+
#endif // os(iOS) && !targetEnvironment(macCatalyst)
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)