Skip to content

Commit acd2d36

Browse files
committed
test(session-replay): Add masking tests for React Native views (#6568)
1 parent 3a184c5 commit acd2d36

11 files changed

+255
-7
lines changed

Sentry.xcodeproj/project.pbxproj

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -805,6 +805,7 @@
805805
D43B26DA2D70A612007747FD /* SentrySpanDataKey.m in Sources */ = {isa = PBXBuildFile; fileRef = D43B26D92D70A60E007747FD /* SentrySpanDataKey.m */; };
806806
D43C1BE82E8FB85400CD5D67 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = D43C1BE72E8FB85400CD5D67 /* SnapshotTesting */; };
807807
D4411DD52E02B74900EA4987 /* ArrayAccessesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4411DD42E02B74100EA4987 /* ArrayAccessesTests.swift */; };
808+
D44311312EB22812006CABE4 /* SentryUIRedactBuilderTests+ReactNative.swift in Sources */ = {isa = PBXBuildFile; fileRef = D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */; };
808809
D44B16722DE464AD006DBDB3 /* TestDispatchFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44B16712DE464A9006DBDB3 /* TestDispatchFactoryTests.swift */; };
809810
D451ED5D2D92ECD200C9BEA8 /* SentryOnDemandReplayError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D451ED5C2D92ECD200C9BEA8 /* SentryOnDemandReplayError.swift */; };
810811
D451ED5F2D92ECDE00C9BEA8 /* SentryReplayFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D451ED5E2D92ECDE00C9BEA8 /* SentryReplayFrame.swift */; };
@@ -2200,6 +2201,7 @@
22002201
D4AF00202D2E92FD00F5F3D7 /* SentryNSFileManagerSwizzling.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSFileManagerSwizzling.m; sourceTree = "<group>"; };
22012202
D4AF00222D2E931000F5F3D7 /* SentryNSFileManagerSwizzling.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryNSFileManagerSwizzling.h; path = include/SentryNSFileManagerSwizzling.h; sourceTree = "<group>"; };
22022203
D4AF00242D2E93C400F5F3D7 /* SentryNSFileManagerSwizzlingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryNSFileManagerSwizzlingTests.m; sourceTree = "<group>"; };
2204+
D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryUIRedactBuilderTests+ReactNative.swift"; sourceTree = "<group>"; };
22032205
D4AF7D292E940492004F0F59 /* SentryUIRedactBuilderTests+Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SentryUIRedactBuilderTests+Common.swift"; sourceTree = "<group>"; };
22042206
D4B0DC7E2DA9257200DE61B6 /* SentryRenderVideoResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryRenderVideoResult.swift; sourceTree = "<group>"; };
22052207
D4BCA0C22DA93C25009E49AB /* SentrySessionReplayIntegration+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SentrySessionReplayIntegration+Test.h"; sourceTree = "<group>"; };
@@ -4251,6 +4253,7 @@
42514253
D82915622C85EF0C00A6CDD4 /* SentryViewPhotographerTests.swift */,
42524254
D8F67AF22BE10F7600C9197B /* SentryUIRedactBuilderTests.swift */,
42534255
D4AF7D292E940492004F0F59 /* SentryUIRedactBuilderTests+Common.swift */,
4256+
D4AF7D212E93FFCA004F0F59 /* SentryUIRedactBuilderTests+ReactNative.swift */,
42544257
D45E2D762E003EBF0072A6B7 /* TestRedactOptions.swift */,
42554258
);
42564259
path = ViewCapture;
@@ -4355,13 +4358,6 @@
43554358
path = Screenshot;
43564359
sourceTree = "<group>";
43574360
};
4358-
D4AF802E2E965188004F0F59 /* __Snapshots__ */ = {
4359-
isa = PBXGroup;
4360-
children = (
4361-
);
4362-
path = __Snapshots__;
4363-
sourceTree = "<group>";
4364-
};
43654361
D4A0C22A2E9E3CE100791353 /* InfoPlist */ = {
43664362
isa = PBXGroup;
43674363
children = (
@@ -4374,6 +4370,13 @@
43744370
path = InfoPlist;
43754371
sourceTree = "<group>";
43764372
};
4373+
D4AF802E2E965188004F0F59 /* __Snapshots__ */ = {
4374+
isa = PBXGroup;
4375+
children = (
4376+
);
4377+
path = __Snapshots__;
4378+
sourceTree = "<group>";
4379+
};
43774380
D4CBA2522DE06D1600581618 /* SentryTestUtilsTests */ = {
43784381
isa = PBXGroup;
43794382
children = (
@@ -6141,6 +6144,7 @@
61416144
7BC6EBF4255C044A0059822A /* SentryEventTests.swift in Sources */,
61426145
63FE721920DA66EC00CDBAE8 /* SentryCrashReportStore_Tests.m in Sources */,
61436146
7B6D98EB24C6E84F005502FA /* SentryCrashInstallationReporterTests.swift in Sources */,
6147+
D44311312EB22812006CABE4 /* SentryUIRedactBuilderTests+ReactNative.swift in Sources */,
61446148
7BA61EA625F21E660008CAA2 /* SentrySDKLogTests.swift in Sources */,
61456149
62CFD9A92C99741100834E1B /* SentryInvalidJSONStringTests.swift in Sources */,
61466150
7BB42EF124F3B7B700D7B39A /* SentrySession+Equality.m 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)