From da8ae24edc310fc04294e7092ea6150417f3a59c Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Wed, 26 Nov 2025 16:10:18 -0500 Subject: [PATCH 1/3] ref: Use Swift integrations --- Sentry.xcodeproj/project.pbxproj | 24 +++------ Sources/Sentry/SentryFeedbackAPIHelper.m | 35 ------------- Sources/Sentry/SentryHub.m | 4 +- Sources/Sentry/SentrySDKInternal.m | 9 +--- .../Sentry/SentryUserFeedbackIntegration.m | 52 ------------------- .../Sentry/include/SentryBaseIntegration.h | 2 +- .../Sentry/include/SentryFeedbackAPIHelper.h | 28 ---------- .../include/SentryIntegrationProtocol.h | 18 +++++-- Sources/Sentry/include/SentryPrivate.h | 2 +- .../include/SentryUserFeedbackIntegration.h | 17 ------ .../Core/Integrations/Integrations.swift | 48 +++++++++++++++++ .../UserFeedback/SentryFeedbackAPI.swift | 8 ++- .../SentryUserFeedbackIntegrationDriver.swift | 20 +++---- .../UserFeedbackIntegration.swift | 36 +++++++++++++ Sources/Swift/SentryDependencyContainer.swift | 4 ++ Tests/SentryTests/SentryHubTests.swift | 2 +- 16 files changed, 130 insertions(+), 179 deletions(-) delete mode 100644 Sources/Sentry/SentryFeedbackAPIHelper.m delete mode 100644 Sources/Sentry/SentryUserFeedbackIntegration.m delete mode 100644 Sources/Sentry/include/SentryFeedbackAPIHelper.h delete mode 100644 Sources/Sentry/include/SentryUserFeedbackIntegration.h create mode 100644 Sources/Swift/Core/Integrations/Integrations.swift create mode 100644 Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 24dad0decbe..43681186264 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -605,8 +605,6 @@ 845C16D52A622A5B00EC9519 /* SentryTracer+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 845C16D42A622A5B00EC9519 /* SentryTracer+Private.h */; }; 845CEAEF2D83F79500B6B325 /* SentryProfilingPublicAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845CEAEE2D83F79500B6B325 /* SentryProfilingPublicAPITests.swift */; }; 845CEB172D8A979700B6B325 /* SentryAppStartProfilingConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845CEB162D8A979700B6B325 /* SentryAppStartProfilingConfigurationTests.swift */; }; - 8482FA9B2DD7C397000E9283 /* SentryFeedbackAPIHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 8482FA992DD7C397000E9283 /* SentryFeedbackAPIHelper.h */; }; - 8482FA9C2DD7C397000E9283 /* SentryFeedbackAPIHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 8482FA9A2DD7C397000E9283 /* SentryFeedbackAPIHelper.m */; }; 848A45192BBF8D33006AAAEC /* SentryContinuousProfiler.mm in Sources */ = {isa = PBXBuildFile; fileRef = 848A45182BBF8D33006AAAEC /* SentryContinuousProfiler.mm */; }; 848A451A2BBF8D33006AAAEC /* SentryContinuousProfiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 848A45172BBF8D33006AAAEC /* SentryContinuousProfiler.h */; }; 848A451D2BBF9504006AAAEC /* SentryProfilerTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 848A451C2BBF9504006AAAEC /* SentryProfilerTestHelpers.m */; }; @@ -650,8 +648,6 @@ 84B7FA4629B2935F00AD93B1 /* ClearTestState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD47B4C268F0B080076A663 /* ClearTestState.swift */; }; 84BA62272CAE2EEF0049F636 /* SentryUserFeedbackWidgetButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BA62262CAE2EEF0049F636 /* SentryUserFeedbackWidgetButtonView.swift */; }; 84CFA4CA2C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CFA4C92C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift */; }; - 84CFA4CD2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */; }; - 84CFA4CE2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */; }; 84DBC62C2CE82F12000C4904 /* SentryFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */; }; 84DEE86B2B686BD400A7BC17 /* SentrySamplerDecision.h in Headers */ = {isa = PBXBuildFile; fileRef = 84DEE86A2B686BD400A7BC17 /* SentrySamplerDecision.h */; }; 84DEE8762B69AD6400A7BC17 /* SentryLaunchProfiling.h in Headers */ = {isa = PBXBuildFile; fileRef = 84DEE8752B69AD6400A7BC17 /* SentryLaunchProfiling.h */; }; @@ -1100,6 +1096,8 @@ FA8E58F12E0AD4270049F69D /* SentryDispatchQueueWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA8E58F02E0AD4220049F69D /* SentryDispatchQueueWrapper.swift */; }; FA90FAA82E06614E008CAAE8 /* SentryExtraPackages.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA90FAA72E06614B008CAAE8 /* SentryExtraPackages.swift */; }; FA90FAFD2E070A3B008CAAE8 /* SentryURLRequestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */; }; + FA914E592ECF968500C54BDD /* UserFeedbackIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA914E532ECF968000C54BDD /* UserFeedbackIntegration.swift */; }; + FA914E5B2ECF988900C54BDD /* Integrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA914E5A2ECF988700C54BDD /* Integrations.swift */; }; FA914E6D2ECFD7D800C54BDD /* SentryFeedbackAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA914E6C2ECFD7D800C54BDD /* SentryFeedbackAPI.swift */; }; FA94E6912E6B92C100576666 /* SentryClientReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA94E68B2E6B92BE00576666 /* SentryClientReport.swift */; }; FA94E6B22E6D265800576666 /* SentryEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA94E6B12E6D265500576666 /* SentryEnvelope.swift */; }; @@ -1965,8 +1963,6 @@ 846F90332D56F59D009E86C1 /* Brewfile-ci-deploy */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = "Brewfile-ci-deploy"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 848214B42E32B10900DF6998 /* SwiftUITestSample_Crash.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SwiftUITestSample_Crash.xctestplan; sourceTree = ""; }; 848214B52E32B10900DF6998 /* SwiftUITestSample_Envelope.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SwiftUITestSample_Envelope.xctestplan; sourceTree = ""; }; - 8482FA992DD7C397000E9283 /* SentryFeedbackAPIHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryFeedbackAPIHelper.h; path = ../../../Sentry/include/SentryFeedbackAPIHelper.h; sourceTree = ""; }; - 8482FA9A2DD7C397000E9283 /* SentryFeedbackAPIHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SentryFeedbackAPIHelper.m; path = ../../../Sentry/SentryFeedbackAPIHelper.m; sourceTree = ""; }; 848A45172BBF8D33006AAAEC /* SentryContinuousProfiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryContinuousProfiler.h; path = ../include/SentryContinuousProfiler.h; sourceTree = ""; }; 848A45182BBF8D33006AAAEC /* SentryContinuousProfiler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SentryContinuousProfiler.mm; sourceTree = ""; }; 848A451B2BBF9504006AAAEC /* SentryProfilerTestHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryProfilerTestHelpers.h; path = ../include/SentryProfilerTestHelpers.h; sourceTree = ""; }; @@ -2006,8 +2002,6 @@ 84BA62262CAE2EEF0049F636 /* SentryUserFeedbackWidgetButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUserFeedbackWidgetButtonView.swift; sourceTree = ""; }; 84C47B2B2A09239100DAEB8A /* .codecov.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .codecov.yml; sourceTree = ""; }; 84CFA4C92C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUserFeedbackWidget.swift; sourceTree = ""; }; - 84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryUserFeedbackIntegration.h; path = ../../../Sentry/include/SentryUserFeedbackIntegration.h; sourceTree = ""; }; - 84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SentryUserFeedbackIntegration.m; path = ../../../Sentry/SentryUserFeedbackIntegration.m; sourceTree = ""; }; 84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFeedback.swift; sourceTree = ""; }; 84DEE86A2B686BD400A7BC17 /* SentrySamplerDecision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentrySamplerDecision.h; path = include/SentrySamplerDecision.h; sourceTree = ""; }; 84DEE8752B69AD6400A7BC17 /* SentryLaunchProfiling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SentryLaunchProfiling.h; path = Sources/Sentry/include/SentryLaunchProfiling.h; sourceTree = SOURCE_ROOT; }; @@ -2485,6 +2479,8 @@ FA8E58F02E0AD4220049F69D /* SentryDispatchQueueWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryDispatchQueueWrapper.swift; sourceTree = ""; }; FA90FAA72E06614B008CAAE8 /* SentryExtraPackages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryExtraPackages.swift; sourceTree = ""; }; FA90FAFC2E070A3B008CAAE8 /* SentryURLRequestFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryURLRequestFactory.swift; sourceTree = ""; }; + FA914E532ECF968000C54BDD /* UserFeedbackIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFeedbackIntegration.swift; sourceTree = ""; }; + FA914E5A2ECF988700C54BDD /* Integrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Integrations.swift; sourceTree = ""; }; FA914E6C2ECFD7D800C54BDD /* SentryFeedbackAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryFeedbackAPI.swift; sourceTree = ""; }; FA94E68B2E6B92BE00576666 /* SentryClientReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryClientReport.swift; sourceTree = ""; }; FA94E6B12E6D265500576666 /* SentryEnvelope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnvelope.swift; sourceTree = ""; }; @@ -4101,12 +4097,9 @@ children = ( 849B8F9E2C70091A00148E1F /* Configuration */, 849B8F962C6E906900148E1F /* SentryUserFeedbackIntegrationDriver.swift */, - 8482FA992DD7C397000E9283 /* SentryFeedbackAPIHelper.h */, - 8482FA9A2DD7C397000E9283 /* SentryFeedbackAPIHelper.m */, FA914E6C2ECFD7D800C54BDD /* SentryFeedbackAPI.swift */, 84DBC62B2CE82F0E000C4904 /* SentryFeedback.swift */, - 84CFA4CB2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h */, - 84CFA4CC2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m */, + FA914E532ECF968000C54BDD /* UserFeedbackIntegration.swift */, 84CFA4C92C9DF884008DA5F4 /* SentryUserFeedbackWidget.swift */, 84B0DFF32CD2CF64007FB332 /* SentryUserFeedbackFormController.swift */, 84A903702D39F66F00690CE4 /* SentryUserFeedbackFormViewModel.swift */, @@ -4995,6 +4988,7 @@ FA67DCD02DDBD4EA00896B02 /* ANR */, FA67DCD22DDBD4EA00896B02 /* FramesTracking */, FA67DCD62DDBD4EA00896B02 /* Performance */, + FA914E5A2ECF988700C54BDD /* Integrations.swift */, ); path = Integrations; sourceTree = ""; @@ -5103,8 +5097,6 @@ 8E133FA625E72EB400ABD0BF /* SentrySamplingContext.h in Headers */, 0A9BF4E428A114B50068D266 /* SentryViewHierarchyIntegration.h in Headers */, D8BBD32728FD9FC00011F850 /* SentrySwift.h in Headers */, - 84CFA4CE2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.h in Headers */, - 8482FA9B2DD7C397000E9283 /* SentryFeedbackAPIHelper.h in Headers */, 8E4E7C7425DAAB49006AB9E2 /* SentrySpanProtocol.h in Headers */, 8EC4CF4A25C38DAA0093DEE9 /* SentrySpanStatus.h in Headers */, 8ECC673D25C23996000E2BF6 /* SentrySpanId.h in Headers */, @@ -5762,6 +5754,7 @@ 628094742D39584C00B3F18B /* SentryUserCodable.swift in Sources */, 631E6D341EBC679C00712345 /* SentryQueueableRequestManager.m in Sources */, 7B8713B426415BAA006D6004 /* SentryAppStartTracker.m in Sources */, + FA914E5B2ECF988900C54BDD /* Integrations.swift in Sources */, 7BDB03BB2513652900BAE198 /* _SentryDispatchQueueWrapperInternal.m in Sources */, FA6FC0A32E0B5ACE00ED2669 /* SentrySdkPackage.swift in Sources */, F48F78692E61DE28009D4E7D /* SentryReachability.swift in Sources */, @@ -5827,7 +5820,6 @@ 848A451D2BBF9504006AAAEC /* SentryProfilerTestHelpers.m in Sources */, 7B63459F280EBA7200CFA05A /* SentryUIEventTracker.m in Sources */, 7BF9EF782722B35D00B5BBEF /* SentrySubClassFinder.m in Sources */, - 84CFA4CD2C9E0CA3008DA5F4 /* SentryUserFeedbackIntegration.m in Sources */, 7BCFA71627D0BB50008C662C /* SentryANRTrackerV1.m in Sources */, 8459FCC02BD73EB20038E9C9 /* SentryProfilerSerialization.m in Sources */, 620078722D38F00D0022CB67 /* SentryGeoCodable.swift in Sources */, @@ -5936,6 +5928,7 @@ 6276350C2D59FACC00F7CEF6 /* SentryEventDecoder.swift in Sources */, 7B6C5EDC264E8DA80010D138 /* SentryFramesTrackingIntegration.m in Sources */, F4FE9E082E6248E40014FED5 /* SentryCrashWrapper.swift in Sources */, + FA914E592ECF968500C54BDD /* UserFeedbackIntegration.swift in Sources */, 849B8F9A2C6E906900148E1F /* SentryUserFeedbackConfiguration.swift in Sources */, 63295AF71EF3C7DB002D4490 /* SentryNSDictionarySanitize.m in Sources */, 7B8ECBFC26498958005FE2EF /* SentryDefaultAppStateManager.m in Sources */, @@ -6141,7 +6134,6 @@ FACEED132E3179A10007B4AC /* SentryOptionsInternal.m in Sources */, FAAB95C02EA163590030A2DB /* SentryScopeObserver.swift in Sources */, D48891D02E98F2E700212823 /* SentryInfoPlistError.swift in Sources */, - 8482FA9C2DD7C397000E9283 /* SentryFeedbackAPIHelper.m in Sources */, 7BC9A20628F41781001E7C4C /* SentryMeasurementUnit.m in Sources */, F429D3AA2E8562EF00DBF387 /* RateLimitParser.swift in Sources */, F4E1E9812E8C2B150007B080 /* SentryDateUtil.swift in Sources */, diff --git a/Sources/Sentry/SentryFeedbackAPIHelper.m b/Sources/Sentry/SentryFeedbackAPIHelper.m deleted file mode 100644 index 694cc7da674..00000000000 --- a/Sources/Sentry/SentryFeedbackAPIHelper.m +++ /dev/null @@ -1,35 +0,0 @@ -#if __has_include() -# import -#elif __has_include() -# import -#else -# import -#endif - -#if TARGET_OS_IOS && SENTRY_HAS_UIKIT - -# import "SentryFeedbackAPIHelper.h" -# import "SentryHub+Private.h" -# import "SentryLogC.h" -# import "SentrySDK+Private.h" -# import "SentryUserFeedbackIntegration.h" - -@implementation SentryFeedbackAPIHelper - -+ (void)showWidget -{ - SentryUserFeedbackIntegration *feedback = [[SentrySDKInternal currentHub] - getInstalledIntegration:[SentryUserFeedbackIntegration class]]; - [feedback showWidget]; -} - -+ (void)hideWidget -{ - SentryUserFeedbackIntegration *feedback = [SentrySDKInternal.currentHub - getInstalledIntegration:[SentryUserFeedbackIntegration class]]; - [feedback hideWidget]; -} - -@end - -#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/SentryHub.m b/Sources/Sentry/SentryHub.m index 58b9385a17e..5c122a27900 100644 --- a/Sources/Sentry/SentryHub.m +++ b/Sources/Sentry/SentryHub.m @@ -894,8 +894,8 @@ - (void)captureLog:(SentryLog *)log #if SENTRY_TARGET_REPLAY_SUPPORTED - (NSString *__nullable)getSessionReplayId { - SentrySessionReplayIntegration *integration = - [self getInstalledIntegration:[SentrySessionReplayIntegration class]]; + SentrySessionReplayIntegration *integration = (SentrySessionReplayIntegration *)[self + getInstalledIntegration:[SentrySessionReplayIntegration class]]; if (integration == nil || integration.sessionReplay == nil) { return nil; } diff --git a/Sources/Sentry/SentrySDKInternal.m b/Sources/Sentry/SentrySDKInternal.m index 71a747f7b79..312cdb379d8 100644 --- a/Sources/Sentry/SentrySDKInternal.m +++ b/Sources/Sentry/SentrySDKInternal.m @@ -26,7 +26,6 @@ #import "SentrySwiftAsyncIntegration.h" #import "SentryTransactionContext.h" #import "SentryUseNSExceptionCallstackWrapper.h" -#import "SentryUserFeedbackIntegration.h" #if SENTRY_HAS_UIKIT # import "SentryAppStartTrackingIntegration.h" @@ -34,7 +33,6 @@ # import "SentryPerformanceTrackingIntegration.h" # import "SentryScreenshotIntegration.h" # import "SentryUIEventTrackingIntegration.h" -# import "SentryUserFeedbackIntegration.h" # import "SentryViewHierarchyIntegration.h" # import "SentryWatchdogTerminationTrackingIntegration.h" #endif // SENTRY_HAS_UIKIT @@ -535,10 +533,6 @@ + (void)endSession [SentryFileIOTrackingIntegration class], [SentryNetworkTrackingIntegration class], [SentrySwiftAsyncIntegration class], nil]; -#if TARGET_OS_IOS && SENTRY_HAS_UIKIT - [defaultIntegrations addObject:[SentryUserFeedbackIntegration class]]; -#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT - #if SENTRY_HAS_METRIC_KIT [defaultIntegrations addObject:[SentryMetricKitIntegration class]]; #endif // SENTRY_HAS_METRIC_KIT @@ -567,7 +561,7 @@ + (void)installIntegrations continue; } - id integrationInstance = [[integrationClass alloc] init]; + id integrationInstance = [[integrationClass alloc] init]; BOOL shouldInstall = [integrationInstance installWithOptions:options]; if (shouldInstall) { SENTRY_LOG_DEBUG(@"Integration installed: %@", NSStringFromClass(integrationClass)); @@ -576,6 +570,7 @@ + (void)installIntegrations name:NSStringFromClass(integrationClass)]; } } + [SentrySwiftIntegrationInstaller installWith:options]; } + (void)reportFullyDisplayed diff --git a/Sources/Sentry/SentryUserFeedbackIntegration.m b/Sources/Sentry/SentryUserFeedbackIntegration.m deleted file mode 100644 index 8aa0f65f961..00000000000 --- a/Sources/Sentry/SentryUserFeedbackIntegration.m +++ /dev/null @@ -1,52 +0,0 @@ -#import "SentryUserFeedbackIntegration.h" -#import "SentryInternalDefines.h" -#import "SentrySDK+Private.h" -#import "SentrySwift.h" - -#if TARGET_OS_IOS && SENTRY_HAS_UIKIT - -@interface SentryUserFeedbackIntegration () -@end - -@implementation SentryUserFeedbackIntegration { - SentryUserFeedbackIntegrationDriver *_driver; -} - -- (BOOL)installWithOptions:(SentryOptions *)options -{ - if (options.userFeedbackConfiguration == nil) { - return NO; - } - - // The screenshot source is coupled to the options, but due to the dependency container being - // tightly to the options anyways, it was decided to not pass it to the container. - SentryScreenshotSource *screenshotSource - = SentryDependencyContainer.sharedInstance.screenshotSource; - _driver = [[SentryUserFeedbackIntegrationDriver alloc] - initWithConfiguration:SENTRY_UNWRAP_NULLABLE(SentryUserFeedbackConfiguration, - options.userFeedbackConfiguration) - delegate:self - screenshotSource:screenshotSource]; - return YES; -} - -- (void)showWidget -{ - [_driver showWidget]; -} - -- (void)hideWidget -{ - [_driver hideWidget]; -} - -// MARK: SentryUserFeedbackIntegrationDriverDelegate - -- (void)captureWithFeedback:(SentryFeedback *)feedback -{ - [SentrySDK captureFeedback:feedback]; -} - -@end - -#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/include/SentryBaseIntegration.h b/Sources/Sentry/include/SentryBaseIntegration.h index 87edbbf5771..3f6ad613be1 100644 --- a/Sources/Sentry/include/SentryBaseIntegration.h +++ b/Sources/Sentry/include/SentryBaseIntegration.h @@ -34,7 +34,7 @@ typedef NS_OPTIONS(NSUInteger, SentryIntegrationOption) { @class SentryOptions; -@interface SentryBaseIntegration : NSObject +@interface SentryBaseIntegration : NSObject - (NSString *)integrationName; - (BOOL)installWithOptions:(SentryOptions *)options; diff --git a/Sources/Sentry/include/SentryFeedbackAPIHelper.h b/Sources/Sentry/include/SentryFeedbackAPIHelper.h deleted file mode 100644 index 0be18864cc2..00000000000 --- a/Sources/Sentry/include/SentryFeedbackAPIHelper.h +++ /dev/null @@ -1,28 +0,0 @@ -#if __has_include() -# import -#elif __has_include() -# import -#else -# import -#endif - -#if TARGET_OS_IOS && SENTRY_HAS_UIKIT - -# import - -NS_ASSUME_NONNULL_BEGIN - -// Needed to access SentryFeedbackIntegration until that class is written in Swift -@interface SentryFeedbackAPIHelper : NSObject - -+ (void)showWidget NS_EXTENSION_UNAVAILABLE( - "Sentry User Feedback UI cannot be used from app extensions."); - -+ (void)hideWidget NS_EXTENSION_UNAVAILABLE( - "Sentry User Feedback UI cannot be used from app extensions."); - -@end - -NS_ASSUME_NONNULL_END - -#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT diff --git a/Sources/Sentry/include/SentryIntegrationProtocol.h b/Sources/Sentry/include/SentryIntegrationProtocol.h index 021d6a7acb7..f326bf1fe7d 100644 --- a/Sources/Sentry/include/SentryIntegrationProtocol.h +++ b/Sources/Sentry/include/SentryIntegrationProtocol.h @@ -4,17 +4,27 @@ NS_ASSUME_NONNULL_BEGIN @class SentryOptions; +// All Integrations conform to this protocol so that the SDK +// can uninstall them when the SDK is closed. @protocol SentryIntegrationProtocol /** - * Installs the integration and returns YES if successful. + * Uninstalls the integration. */ -- (BOOL)installWithOptions:(SentryOptions *)options NS_SWIFT_NAME(install(with:)); +- (void)uninstall; + +@end + +// ObjC integrations conform to this protocol. This should not be used for new +// integrations. New ones should be written in Swift and use the `Sentry.Integration` +// protocol. The main difference is that the ObjC protocol does not inject dependencies +// so conformers need to access the singleton. +@protocol SentryObjCIntegrationProtocol /** - * Uninstalls the integration. + * Installs the integration and returns YES if successful. */ -- (void)uninstall; +- (BOOL)installWithOptions:(SentryOptions *)options NS_SWIFT_NAME(install(with:)); @end diff --git a/Sources/Sentry/include/SentryPrivate.h b/Sources/Sentry/include/SentryPrivate.h index 4386c5d7875..1c5279e9db6 100644 --- a/Sources/Sentry/include/SentryPrivate.h +++ b/Sources/Sentry/include/SentryPrivate.h @@ -22,6 +22,7 @@ #import "SentryEnvelopeAttachmentHeader.h" #import "SentryEventSwiftHelper.h" #import "SentryHub+Private.h" +#import "SentryIntegrationProtocol.h" #import "SentryNSDataUtils.h" #import "SentrySDK+Private.h" #import "SentryTime.h" @@ -47,7 +48,6 @@ #import "SentryDelayedFramesTracker.h" #import "SentryDependencyContainerSwiftHelper.h" #import "SentryDeviceContextKeys.h" -#import "SentryFeedbackAPIHelper.h" #import "SentryFileIOTrackerHelper.h" #import "SentryFileManagerHelper.h" #import "SentryMeta.h" diff --git a/Sources/Sentry/include/SentryUserFeedbackIntegration.h b/Sources/Sentry/include/SentryUserFeedbackIntegration.h deleted file mode 100644 index 2ac1edd2f8b..00000000000 --- a/Sources/Sentry/include/SentryUserFeedbackIntegration.h +++ /dev/null @@ -1,17 +0,0 @@ -#import "SentryBaseIntegration.h" - -#import "SentryDefines.h" - -#if TARGET_OS_IOS && SENTRY_HAS_UIKIT - -NS_ASSUME_NONNULL_BEGIN - -NS_EXTENSION_UNAVAILABLE("Sentry User Feedback UI cannot be used from app extensions.") -@interface SentryUserFeedbackIntegration : SentryBaseIntegration -- (void)showWidget; -- (void)hideWidget; -@end - -NS_ASSUME_NONNULL_END - -#endif // TARGET_OS_IOS && SENTRY_HAS_UIKIT diff --git a/Sources/Swift/Core/Integrations/Integrations.swift b/Sources/Swift/Core/Integrations/Integrations.swift new file mode 100644 index 00000000000..b265df7ce63 --- /dev/null +++ b/Sources/Swift/Core/Integrations/Integrations.swift @@ -0,0 +1,48 @@ +@_implementationOnly import _SentryPrivate + +// The Swift counterpart to `SentryObjcIntegrationProtocol`. This protocol allows +// injecting the dependencies in a way that does not require the Integration to +// depend on SentryDependencyContainer. +protocol SwiftIntegration: SentryIntegrationProtocol { + // The dependencies required for the integration. The easiest way to satisfy this requirement when migrating from ObjC + // is to define it as `SentryDependencyContainer` with a typealias. However, a generic Swift class that has a protocol + // constraint on the `Dependencies` type can make it easier to test and to use without a direct dependency on all + // of `SentryDependencyContainer`. + associatedtype Dependencies + + // The initializer is failable, return nil if the integration was not installed, for example when the options that enable the integration is not enabled. + init?(with options: Options, dependencies: Dependencies) + + // Name of the integration that is used by `SentrySdkInfo` + static var name: String { get } +} + +// Type erases the `Integration` so that it can be stored in an array and used for `addInstalledIntegration` +private struct AnyIntegration { + let install: (Options, SentryDependencyContainer) -> SentryIntegrationProtocol? + let name: String + + init(_ integration: I.Type) where I.Dependencies == SentryDependencyContainer { + name = I.name + install = { + integration.init(with: $0, dependencies: $1) + } + } +} + +// Bridges to ObjC code to trigger installing the integrations +@_spi(Private) @objc public final class SentrySwiftIntegrationInstaller: NSObject { + @objc public class func install(with options: Options) { + let dependencies = SentryDependencyContainer.sharedInstance() + #if os(iOS) && !SENTRY_NO_UIKIT + let integrations: [AnyIntegration] = [.init(UserFeedbackIntegration.self)] + #else + let integrations: [AnyIntegration] = [] + #endif + integrations.forEach { anyIntegration in + guard let integration = anyIntegration.install(options, dependencies) else { return } + + SentrySDKInternal.currentHub().addInstalledIntegration(integration, name: anyIntegration.name) + } + } +} diff --git a/Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.swift b/Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.swift index 68af8f953db..c5556edde88 100644 --- a/Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.swift +++ b/Sources/Swift/Integrations/UserFeedback/SentryFeedbackAPI.swift @@ -10,7 +10,7 @@ public final class SentryFeedbackAPI: NSObject { /// - seealso: See `SentryOptions.configureUserFeedback` to configure the widget. @available(iOSApplicationExtension, unavailable) @objc public func showWidget() { - SentryFeedbackAPIHelper.showWidget() + getIntegration()?.driver.showWidget() } /// Hide the feedback widget button. @@ -18,7 +18,11 @@ public final class SentryFeedbackAPI: NSObject { /// - seealso: See `SentryOptions.configureUserFeedback` to configure the widget. @available(iOSApplicationExtension, unavailable) @objc public func hideWidget() { - SentryFeedbackAPIHelper.hideWidget() + getIntegration()?.driver.hideWidget() + } + + private func getIntegration() -> UserFeedbackIntegration? { + SentrySDKInternal.currentHub().getInstalledIntegration(UserFeedbackIntegration.self) as? UserFeedbackIntegration } } #endif diff --git a/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift b/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift index 64b8498db40..57862bf7c62 100644 --- a/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift +++ b/Sources/Swift/Integrations/UserFeedback/SentryUserFeedbackIntegrationDriver.swift @@ -3,27 +3,21 @@ import Foundation @_implementationOnly import _SentryPrivate import UIKit -@objc -@_spi(Private) public protocol SentryUserFeedbackIntegrationDriverDelegate: NSObjectProtocol { - func capture(feedback: SentryFeedback) -} - /** * An integration managing a workflow for end users to report feedback via Sentry. * - note: The default method to show the feedback form is via a floating widget placed in the bottom trailing corner of the screen. See the configuration classes for alternative options. */ @available(iOSApplicationExtension, unavailable) -@objcMembers -@_spi(Private) public class SentryUserFeedbackIntegrationDriver: NSObject { +final class SentryUserFeedbackIntegrationDriver: NSObject { let configuration: SentryUserFeedbackConfiguration private var widget: SentryUserFeedbackWidget? - weak var delegate: (any SentryUserFeedbackIntegrationDriverDelegate)? + fileprivate let callback: (SentryFeedback) -> Void let screenshotSource: SentryScreenshotSource weak var customButton: UIButton? - @_spi(Private) public init(configuration: SentryUserFeedbackConfiguration, delegate: any SentryUserFeedbackIntegrationDriverDelegate, screenshotSource: SentryScreenshotSource) { + init(configuration: SentryUserFeedbackConfiguration, screenshotSource: SentryScreenshotSource, callback: @escaping (SentryFeedback) -> Void) { self.configuration = configuration - self.delegate = delegate + self.callback = callback self.screenshotSource = screenshotSource super.init() @@ -64,7 +58,7 @@ import UIKit customButton?.removeTarget(self, action: #selector(showForm(sender:)), for: .touchUpInside) } - @objc public func showWidget() { + func showWidget() { if widget == nil { widget = SentryUserFeedbackWidget(config: configuration, delegate: self) } @@ -72,7 +66,7 @@ import UIKit widget?.rootVC.setWidget(visible: true, animated: configuration.animations) } - @objc public func hideWidget() { + func hideWidget() { widget?.rootVC.setWidget(visible: false, animated: configuration.animations) } @@ -88,7 +82,7 @@ import UIKit extension SentryUserFeedbackIntegrationDriver: SentryUserFeedbackFormDelegate { func finished(with feedback: SentryFeedback?) { if let feedback = feedback { - delegate?.capture(feedback: feedback) + callback(feedback) } presenter?.dismiss(animated: configuration.animations) { self.configuration.onFormClose?() diff --git a/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift b/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift new file mode 100644 index 00000000000..b3d5600d03c --- /dev/null +++ b/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift @@ -0,0 +1,36 @@ +@_implementationOnly import _SentryPrivate + +#if os(iOS) && !SENTRY_NO_UIKIT + +protocol ScreenshotSourceProvider { + var screenshotSource: SentryScreenshotSource? { get } +} + +final class UserFeedbackIntegration: NSObject, SwiftIntegration { + + let driver: SentryUserFeedbackIntegrationDriver + + init?(with options: Options, dependencies: Dependencies) { + guard let configuration = options.userFeedbackConfiguration else { + return nil + } + + // The screenshot source is coupled to the options, but due to the dependency container being + // tightly to the options anyways, it was decided to not pass it to the container. + guard let screenshotSource = dependencies.screenshotSource else { + return nil + } + + driver = SentryUserFeedbackIntegrationDriver(configuration: configuration, screenshotSource: screenshotSource) { feedback in + SentrySDK.capture(feedback: feedback) + } + } + + func uninstall() { } + + static var name: String { + "SentryUserFeedbackIntegration" + } +} + +#endif diff --git a/Sources/Swift/SentryDependencyContainer.swift b/Sources/Swift/SentryDependencyContainer.swift index e3e586b90ad..8f8b441b658 100644 --- a/Sources/Swift/SentryDependencyContainer.swift +++ b/Sources/Swift/SentryDependencyContainer.swift @@ -252,3 +252,7 @@ extension SentryFileManager: SentryFileManagerProtocol { } } } } + +#if os(iOS) && !SENTRY_NO_UIKIT +extension SentryDependencyContainer: ScreenshotSourceProvider { } +#endif diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index a3415b9acff..08dd1a5b438 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -1395,7 +1395,7 @@ class SentryHubTests: XCTestCase { let integration = EmptyIntegration() sut.addInstalledIntegration(integration, name: "EmptyIntegration") - let installedIntegration = sut.getInstalledIntegration(EmptyIntegration.self) + let installedIntegration = sut.getInstalledIntegration(EmptyIntegration.self) as? NSObject XCTAssert(integration === installedIntegration) } From 853000a0b540f061bbf9db57c3ab6887cf319cb3 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Wed, 26 Nov 2025 19:21:07 -0500 Subject: [PATCH 2/3] Add tests --- Sentry.xcodeproj/project.pbxproj | 4 ++ .../UserFeedbackIntegrationTests.swift | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 Tests/SentryTests/Integrations/Feedback/UserFeedbackIntegrationTests.swift diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index 43681186264..f26184912a9 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -1104,6 +1104,7 @@ FA94E7242E6F339400576666 /* SentryEnvelopeItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA94E7232E6F32FA00576666 /* SentryEnvelopeItemType.swift */; }; FAA414532ED7615D00B269CD /* SentrySessionReplayHybridSDK.m in Sources */ = {isa = PBXBuildFile; fileRef = FAA414502ED7608E00B269CD /* SentrySessionReplayHybridSDK.m */; }; FAA414542ED7616800B269CD /* SentrySessionReplayHybridSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = D80382BE2C09C6FD0090E048 /* SentrySessionReplayHybridSDK.h */; settings = {ATTRIBUTES = (Private, ); }; }; + FAA415552ED7CE7300B269CD /* UserFeedbackIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAA4154F2ED7CE6A00B269CD /* UserFeedbackIntegrationTests.swift */; }; FAAB29F12E3D252300ACD577 /* SentrySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAB29F02E3D252000ACD577 /* SentrySession.swift */; }; FAAB2EE02E4BE97500FE8B7E /* TestSentryNSApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAB2EDF2E4BE96F00FE8B7E /* TestSentryNSApplication.swift */; }; FAAB2F972E4D345800FE8B7E /* SentryUIDeviceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAAB2F962E4D344F00FE8B7E /* SentryUIDeviceWrapper.swift */; }; @@ -2486,6 +2487,7 @@ FA94E6B12E6D265500576666 /* SentryEnvelope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnvelope.swift; sourceTree = ""; }; FA94E7232E6F32FA00576666 /* SentryEnvelopeItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryEnvelopeItemType.swift; sourceTree = ""; }; FAA414502ED7608E00B269CD /* SentrySessionReplayHybridSDK.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SentrySessionReplayHybridSDK.m; sourceTree = ""; }; + FAA4154F2ED7CE6A00B269CD /* UserFeedbackIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFeedbackIntegrationTests.swift; sourceTree = ""; }; FAAB29F02E3D252000ACD577 /* SentrySession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySession.swift; sourceTree = ""; }; FAAB2EDF2E4BE96F00FE8B7E /* TestSentryNSApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSentryNSApplication.swift; sourceTree = ""; }; FAAB2F962E4D344F00FE8B7E /* SentryUIDeviceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUIDeviceWrapper.swift; sourceTree = ""; }; @@ -4045,6 +4047,7 @@ 843FB3422D156B9900558F18 /* Feedback */ = { isa = PBXGroup; children = ( + FAA4154F2ED7CE6A00B269CD /* UserFeedbackIntegrationTests.swift */, 843FB3412D156B9900558F18 /* SentryFeedbackTests.swift */, ); path = Feedback; @@ -6391,6 +6394,7 @@ 7BC6EBF8255C05060059822A /* TestData.swift in Sources */, 92235CB02E155B2600865983 /* SentryLoggerTests.swift in Sources */, 7B88F30224BC5C6D00ADF90A /* SentrySdkInfoTests.swift in Sources */, + FAA415552ED7CE7300B269CD /* UserFeedbackIntegrationTests.swift in Sources */, 7BC8523B2458849E005A70F0 /* SentryDataCategoryMapperTests.swift in Sources */, 63FE721220DA66EC00CDBAE8 /* SentryCrashMach_Tests.m in Sources */, 0A94158228F6C4C2006A5DD1 /* SentryAppStateManagerTests.swift in Sources */, diff --git a/Tests/SentryTests/Integrations/Feedback/UserFeedbackIntegrationTests.swift b/Tests/SentryTests/Integrations/Feedback/UserFeedbackIntegrationTests.swift new file mode 100644 index 00000000000..f4803a41837 --- /dev/null +++ b/Tests/SentryTests/Integrations/Feedback/UserFeedbackIntegrationTests.swift @@ -0,0 +1,51 @@ +@_spi(Private) @testable import Sentry +import XCTest + +#if os(iOS) + +final class UserFeedbackIntegrationTests: XCTestCase { + + static private var optionsWithFeedback: Options { + let options = Options() + options.configureUserFeedback = { _ in } + return options + } + + static private var optionsWithoutFeedback: Options { + return Options() + } + + static private var screenshotSource: SentryScreenshotSource { + let viewRenderer = SentryDefaultViewRenderer() + let photographer = SentryViewPhotographer( + renderer: viewRenderer, + redactOptions: Options().screenshot, + enableMaskRendererV2: false) + return SentryScreenshotSource(photographer: photographer) + } + + private struct MockDependencies: ScreenshotSourceProvider { + let screenshotSource: SentryScreenshotSource? + } + + func testUsesCorrectName() { + XCTAssertEqual(UserFeedbackIntegration.name, "SentryUserFeedbackIntegration") + } + + func testInitializerFailsWhenNoScreenshotSource() { + let integration = UserFeedbackIntegration(with: Self.optionsWithFeedback, dependencies: MockDependencies(screenshotSource: nil)) + XCTAssertNil(integration) + } + + func testInitializerSucceedsWhenScreenshotSourceIsPresent() { + let integration = UserFeedbackIntegration(with: Self.optionsWithFeedback, dependencies: MockDependencies(screenshotSource: Self.screenshotSource)) + XCTAssertNotNil(integration) + } + + func testInitializerFailsWhenFeedbackNotConfigured() { + let integration = UserFeedbackIntegration(with: Self.optionsWithoutFeedback, dependencies: MockDependencies(screenshotSource: nil)) + XCTAssertNil(integration) + } +} + +#endif From 6f2de9e6cfb4149a74115d7acb79874265f1cf11 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Fri, 28 Nov 2025 10:14:22 -0800 Subject: [PATCH 3/3] PR feedback --- .../UserFeedbackIntegration.swift | 2 +- .../UserFeedbackIntegrationTests.swift | 29 +++++++------------ 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift b/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift index b3d5600d03c..3dc8e2fdffb 100644 --- a/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift +++ b/Sources/Swift/Integrations/UserFeedback/UserFeedbackIntegration.swift @@ -26,7 +26,7 @@ final class UserFeedbackIntegration: NSO } } - func uninstall() { } + func uninstall() { /* Empty on purpose. Nothing to uninstall. */ } static var name: String { "SentryUserFeedbackIntegration" diff --git a/Tests/SentryTests/Integrations/Feedback/UserFeedbackIntegrationTests.swift b/Tests/SentryTests/Integrations/Feedback/UserFeedbackIntegrationTests.swift index f4803a41837..5401e8da27d 100644 --- a/Tests/SentryTests/Integrations/Feedback/UserFeedbackIntegrationTests.swift +++ b/Tests/SentryTests/Integrations/Feedback/UserFeedbackIntegrationTests.swift @@ -11,39 +11,32 @@ final class UserFeedbackIntegrationTests: XCTestCase { return options } - static private var optionsWithoutFeedback: Options { - return Options() - } - - static private var screenshotSource: SentryScreenshotSource { - let viewRenderer = SentryDefaultViewRenderer() - let photographer = SentryViewPhotographer( - renderer: viewRenderer, - redactOptions: Options().screenshot, - enableMaskRendererV2: false) - return SentryScreenshotSource(photographer: photographer) - } - - private struct MockDependencies: ScreenshotSourceProvider { + private struct TestDependencies: ScreenshotSourceProvider { let screenshotSource: SentryScreenshotSource? } func testUsesCorrectName() { - XCTAssertEqual(UserFeedbackIntegration.name, "SentryUserFeedbackIntegration") + XCTAssertEqual(UserFeedbackIntegration.name, "SentryUserFeedbackIntegration") } func testInitializerFailsWhenNoScreenshotSource() { - let integration = UserFeedbackIntegration(with: Self.optionsWithFeedback, dependencies: MockDependencies(screenshotSource: nil)) + let integration = UserFeedbackIntegration(with: Self.optionsWithFeedback, dependencies: TestDependencies(screenshotSource: nil)) XCTAssertNil(integration) } func testInitializerSucceedsWhenScreenshotSourceIsPresent() { - let integration = UserFeedbackIntegration(with: Self.optionsWithFeedback, dependencies: MockDependencies(screenshotSource: Self.screenshotSource)) + let viewRenderer = SentryDefaultViewRenderer() + let photographer = SentryViewPhotographer( + renderer: viewRenderer, + redactOptions: Options().screenshot, + enableMaskRendererV2: false) + let screenshotSource = SentryScreenshotSource(photographer: photographer) + let integration = UserFeedbackIntegration(with: Self.optionsWithFeedback, dependencies: TestDependencies(screenshotSource: screenshotSource)) XCTAssertNotNil(integration) } func testInitializerFailsWhenFeedbackNotConfigured() { - let integration = UserFeedbackIntegration(with: Self.optionsWithoutFeedback, dependencies: MockDependencies(screenshotSource: nil)) + let integration = UserFeedbackIntegration(with: Options(), dependencies: TestDependencies(screenshotSource: nil)) XCTAssertNil(integration) } }