diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift index a2b7ead57d..6babea37b3 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Fixtures.swift @@ -11,6 +11,7 @@ public enum Fixture: CaseIterable { case basicShapes case basicTexts case sliders + case progressViews case segments case pickers case switches @@ -54,6 +55,8 @@ public enum Fixture: CaseIterable { return UIStoryboard.basic.instantiateViewController(withIdentifier: "Texts") case .sliders: return UIStoryboard.inputElements.instantiateViewController(withIdentifier: "Sliders") + case .progressViews: + return UIStoryboard.inputElements.instantiateViewController(withIdentifier: "ProgressViews") case .segments: return UIStoryboard.inputElements.instantiateViewController(withIdentifier: "Segments") case .pickers: diff --git a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/InputElements.storyboard b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/InputElements.storyboard index 24627f63bf..88fe7c2bd4 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/InputElements.storyboard +++ b/DatadogSessionReplay/SRSnapshotTests/SRFixtures/Sources/SRFixtures/Resources/Storyboards/InputElements.storyboard @@ -1,9 +1,8 @@ - + - - + @@ -544,6 +543,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -561,6 +650,12 @@ + + + + + + @@ -570,6 +665,9 @@ + + + diff --git a/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift b/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift index 9b2b947dff..3f196d1585 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRHost/MenuViewController.swift @@ -16,6 +16,8 @@ internal extension Fixture { return "Basic Texts" case .sliders: return "Sliders" + case .progressViews: + return "Progress Views" case .segments: return "Segments" case .pickers: diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift index f70310ffbe..72714a2ed9 100644 --- a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/SRSnapshotTests.swift @@ -54,6 +54,19 @@ final class SRSnapshotTests: SnapshotTestCase { } } + func testProgressViews() throws { + show(fixture: .progressViews) + + try forPrivacyModes([.allow, .mask]) { privacyMode in + let image = try takeSnapshot(with: privacyMode) + DDAssertSnapshotTest( + newImage: image, + snapshotLocation: .folder(named: snapshotsFolderPath, fileNameSuffix: "-\(privacyMode)-privacy"), + record: recordingMode + ) + } + } + func testSegments() throws { show(fixture: .segments) diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testProgressViews()-allow-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testProgressViews()-allow-privacy.png.json new file mode 100644 index 0000000000..55525b17dd --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testProgressViews()-allow-privacy.png.json @@ -0,0 +1 @@ +{"hash":"b9d1161cdce6da4d4d314ba64300feaf84dcf6c3"} \ No newline at end of file diff --git a/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testProgressViews()-mask-privacy.png.json b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testProgressViews()-mask-privacy.png.json new file mode 100644 index 0000000000..214f463c0a --- /dev/null +++ b/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_/pointers/testProgressViews()-mask-privacy.png.json @@ -0,0 +1 @@ +{"hash":"b7ede114b25c7c4bf5fcb8e9434652a097ee8989"} \ No newline at end of file diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorder.swift new file mode 100644 index 0000000000..a2e5ae2ab6 --- /dev/null +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorder.swift @@ -0,0 +1,78 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +#if os(iOS) +import UIKit + +internal struct UIProgressViewRecorder: NodeRecorder { + let identifier = UUID() + + func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { + guard let progressView = view as? UIProgressView else { + return nil + } + + guard attributes.isVisible else { + return InvisibleElement.constant + } + + let ids = context.ids.nodeIDs(2, view: progressView, nodeRecorder: self) + + let builder = UIProgressViewWireframesBuilder( + wireframeRect: attributes.frame, + attributes: attributes, + backgroundWireframeID: ids[0], + progressTrackWireframeID: ids[1], + progress: progressView.progress, + progressTintColor: progressView.progressTintColor?.cgColor ?? progressView.tintColor.cgColor, + backgroundColor: progressView.trackTintColor?.cgColor ?? progressView.backgroundColor?.cgColor + ) + let node = Node(viewAttributes: attributes, wireframesBuilder: builder) + return SpecificElement(subtreeStrategy: .ignore, nodes: [node]) + } +} + +internal struct UIProgressViewWireframesBuilder: NodeWireframesBuilder { + var wireframeRect: CGRect + let attributes: ViewAttributes + + let backgroundWireframeID: WireframeID + let progressTrackWireframeID: WireframeID + let progress: Float + let progressTintColor: CGColor + let backgroundColor: CGColor? + + func buildWireframes(with builder: WireframesBuilder) -> [SRWireframe] { + guard progress >= 0 && progress <= 1 else { + return [] // illegal, should not happen + } + + let background = builder.createShapeWireframe( + id: backgroundWireframeID, + frame: wireframeRect, + backgroundColor: backgroundColor ?? SystemColors.tertiarySystemFill, + cornerRadius: wireframeRect.height/2 + ) + + // Create progress wireframe + let (progressRect, _) = wireframeRect + .divided(atDistance: CGFloat(progress) * wireframeRect.width, from: .minXEdge) + let progressTrackFrame = progressRect.putInside(wireframeRect, horizontalAlignment: .left, verticalAlignment: .middle) + + let progressTrack = builder.createShapeWireframe( + id: progressTrackWireframeID, + frame: progressTrackFrame, + borderColor: nil, + borderWidth: nil, + backgroundColor: progressTintColor, + cornerRadius: wireframeRect.height/2, + opacity: attributes.alpha + ) + + return [background, progressTrack] + } +} +#endif diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorder.swift index 08edf2e978..fbed39de28 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorder.swift @@ -16,7 +16,6 @@ internal struct UnsupportedViewRecorder: NodeRecorder { { _, context in context.viewControllerContext.isRootView(of: .safari) }, { _, context in context.viewControllerContext.isRootView(of: .activity) }, { _, context in context.viewControllerContext.isRootView(of: .swiftUI) }, - { view, _ in view is UIProgressView }, { view, _ in view is UIActivityIndicatorView } ] // swiftlint:enable opening_brace diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift index 89e2e56075..d4c1287d1c 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift @@ -73,7 +73,8 @@ internal func createDefaultNodeRecorders() -> [NodeRecorder] { UITabBarRecorder(), UIPickerViewRecorder(), UIDatePickerRecorder(), - WKWebViewRecorder() + WKWebViewRecorder(), + UIProgressViewRecorder() ] } #endif diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorderTests.swift index 841147c827..03bb980cff 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorderTests.swift @@ -17,10 +17,10 @@ class UnsupportedViewRecorderTests: XCTestCase { private let recorder = UnsupportedViewRecorder() private let unsupportedViews: [UIView] = [ - UIProgressView(), UIActivityIndicatorView() + UIActivityIndicatorView() ].compactMap { $0 } private let expectedUnsupportedViewsClassNames = [ - "UIProgressView", "UIActivityIndicatorView", "WKWebView" + "UIActivityIndicatorView", "WKWebView" ] private let otherViews = [UILabel(), UIView(), UIImageView(), UIScrollView(), WKWebView()]