From 775315ca8d8dcf62c813dbfae70ee1043733f115 Mon Sep 17 00:00:00 2001 From: Brandon Stalnaker Date: Mon, 17 Nov 2025 08:41:42 -0500 Subject: [PATCH 1/5] feat: Implement Manual SceneDelegate Support --- .../MParticleSceneDelegateTests.swift | 123 ++++++++++++++++++ mParticle-Apple-SDK.xcodeproj/project.pbxproj | 6 + mParticle-Apple-SDK/Include/mParticle.h | 28 +++- mParticle-Apple-SDK/mParticle.m | 40 ++++++ 4 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift diff --git a/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift b/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift new file mode 100644 index 000000000..80039fa51 --- /dev/null +++ b/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift @@ -0,0 +1,123 @@ +// +// MParticleSceneDelegateTests.swift +// mParticle-Apple-SDK +// +// Created by Brandon Stalnaker on 11/13/25. +// + +#if MPARTICLE_LOCATION_DISABLE +import mParticle_Apple_SDK_NoLocation +#else +import mParticle_Apple_SDK +#endif +import XCTest + +@available(iOS 13.0, *) +final class MParticleSceneDelegateTests: MParticleTestBase { + + // MARK: - Properties + + var testURL: URL! + var testUserActivity: NSUserActivity! + + override func setUp() { + super.setUp() + testURL = URL(string: "myapp://test/path?param=value")! + testUserActivity = NSUserActivity(activityType: "com.test.activity") + testUserActivity.title = "Test Activity" + testUserActivity.userInfo = ["key": "value"] + + // The implementation calls [MParticle sharedInstance], so we need to set the mock on the shared instance + MParticle.sharedInstance().appNotificationHandler = appNotificationHandler + + // Reset mock state for each test + appNotificationHandler.continueUserActivityCalled = false + appNotificationHandler.continueUserActivityUserActivityParam = nil + appNotificationHandler.continueUserActivityRestorationHandlerParam = nil + appNotificationHandler.openURLWithOptionsCalled = false + appNotificationHandler.openURLWithOptionsURLParam = nil + appNotificationHandler.openURLWithOptionsOptionsParam = nil + } + + // MARK: - Method Existence Tests + + func test_handleURLContext_methodExists() { + // Verify the method exists with correct signature + XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) + } + + func test_handleUserActivity_methodExists() { + // Verify the method exists with correct signature + XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleUserActivity(_:)))) + } + + // MARK: - handleURLContext Tests + // Note: Testing with mock URLContext is complex due to system class limitations + // These tests focus on method availability and basic functionality + + func test_handleURLContext_availabilityCheck() { + // Verify the method is only available on iOS 13+ + if #available(iOS 13.0, *) { + XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) + } else { + XCTAssertFalse(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) + } + } + + // MARK: - handleUserActivity Tests + + func test_handleUserActivity_invokesAppNotificationHandler() { + // Act + mparticle.handleUserActivity(testUserActivity) + + // Assert - handleUserActivity directly calls the app notification handler + XCTAssertTrue(appNotificationHandler.continueUserActivityCalled) + XCTAssertEqual(appNotificationHandler.continueUserActivityUserActivityParam, testUserActivity) + XCTAssertNotNil(appNotificationHandler.continueUserActivityRestorationHandlerParam) + } + + func test_handleUserActivity_withWebBrowsingActivity() { + // Arrange + let webActivity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb) + webActivity.title = "Web Page" + webActivity.webpageURL = URL(string: "https://example.com/page") + + // Act + mparticle.handleUserActivity(webActivity) + + // Assert - Direct call to app notification handler + XCTAssertTrue(appNotificationHandler.continueUserActivityCalled) + XCTAssertEqual(appNotificationHandler.continueUserActivityUserActivityParam, webActivity) + } + + func test_handleUserActivity_restorationHandlerIsEmpty() { + // Act + mparticle.handleUserActivity(testUserActivity) + + // Assert + XCTAssertTrue(appNotificationHandler.continueUserActivityCalled) + + // Verify the restoration handler is provided and safe to call + let restorationHandler = appNotificationHandler.continueUserActivityRestorationHandlerParam + XCTAssertNotNil(restorationHandler) + + // Test that calling the restoration handler doesn't crash + XCTAssertNoThrow(restorationHandler?(nil)) + XCTAssertNoThrow(restorationHandler?([])) + } + + // MARK: - iOS Version Availability Tests + + func test_handleUserActivity_alwaysAvailable() { + // handleUserActivity should be available on all iOS versions + XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleUserActivity(_:)))) + } + + // MARK: - Integration Tests + + func test_bothMethods_exist() { + // Verify both SceneDelegate support methods exist + XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) + XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleUserActivity(_:)))) + } +} diff --git a/mParticle-Apple-SDK.xcodeproj/project.pbxproj b/mParticle-Apple-SDK.xcodeproj/project.pbxproj index ce47935b6..be89327c8 100644 --- a/mParticle-Apple-SDK.xcodeproj/project.pbxproj +++ b/mParticle-Apple-SDK.xcodeproj/project.pbxproj @@ -566,6 +566,8 @@ 7E0387842DB913D2003B7D5E /* MPRokt.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E0387802DB913D2003B7D5E /* MPRokt.m */; }; 7E15B2062D94617900C1FF3E /* MPRoktTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E15B2052D94617900C1FF3E /* MPRoktTests.m */; }; 7E15B2072D94617900C1FF3E /* MPRoktTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E15B2052D94617900C1FF3E /* MPRoktTests.m */; }; + 7E573D2A2ECB65D90087185D /* MParticleSceneDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E573D292ECB65D90087185D /* MParticleSceneDelegateTests.swift */; }; + 7E573D2B2ECB65D90087185D /* MParticleSceneDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E573D292ECB65D90087185D /* MParticleSceneDelegateTests.swift */; }; B31360F92E012760000DFBC9 /* MPRoktEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31360F72E012760000DFBC9 /* MPRoktEvent.swift */; }; B3D778622E02F55F00D887A4 /* MPRoktEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31360F72E012760000DFBC9 /* MPRoktEvent.swift */; }; D30CD0CB2CFF5FB100F5148A /* MPStateMachine.h in Headers */ = {isa = PBXBuildFile; fileRef = 53A79B0629CDFB1F00E7489F /* MPStateMachine.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -923,6 +925,7 @@ 7E03877F2DB913D2003B7D5E /* MPRokt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPRokt.h; sourceTree = ""; }; 7E0387802DB913D2003B7D5E /* MPRokt.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPRokt.m; sourceTree = ""; }; 7E15B2052D94617900C1FF3E /* MPRoktTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPRoktTests.m; sourceTree = ""; }; + 7E573D292ECB65D90087185D /* MParticleSceneDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MParticleSceneDelegateTests.swift; sourceTree = ""; }; B31360F72E012760000DFBC9 /* MPRoktEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPRoktEvent.swift; sourceTree = ""; }; D33C8B312B8510C20012EDFD /* MPAudience.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPAudience.h; sourceTree = ""; }; D33C8B322B8510C20012EDFD /* MPAudience.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MPAudience.m; sourceTree = ""; }; @@ -1504,6 +1507,7 @@ isa = PBXGroup; children = ( 7231B8342EB95F9F001565E5 /* MParticleBreadcrumbTests.swift */, + 7E573D292ECB65D90087185D /* MParticleSceneDelegateTests.swift */, 7231B83E2EB9627D001565E5 /* MParticleErrorTests.swift */, 7231B83B2EB961F2001565E5 /* MParticleLTVTests.swift */, 7231B84B2EB963B3001565E5 /* MParticleKitBatchTests.swift */, @@ -1910,6 +1914,7 @@ 534CD26929CE2CE1008452B3 /* MPSurrogateAppDelegateTests.m in Sources */, 534CD26A29CE2CE1008452B3 /* MPGDPRConsentTests.m in Sources */, 356D4A5A2E58B09D00CB69FE /* SettingsProviderMock.swift in Sources */, + 7E573D2A2ECB65D90087185D /* MParticleSceneDelegateTests.swift in Sources */, 534CD26B29CE2CE1008452B3 /* MPBackendControllerTests.m in Sources */, 534CD26C29CE2CE1008452B3 /* MPKitConfigurationTests.mm in Sources */, 7231B80E2EB3C4AC001565E5 /* MPKitMock.swift in Sources */, @@ -2129,6 +2134,7 @@ 53A79CD129CE019F00E7489F /* MPSurrogateAppDelegateTests.m in Sources */, 53A79CE729CE019F00E7489F /* MPGDPRConsentTests.m in Sources */, 356D4A592E58B09D00CB69FE /* SettingsProviderMock.swift in Sources */, + 7E573D2B2ECB65D90087185D /* MParticleSceneDelegateTests.swift in Sources */, 53A79CD229CE019F00E7489F /* MPBackendControllerTests.m in Sources */, 53A79CF329CE019F00E7489F /* MPKitConfigurationTests.mm in Sources */, 7231B80D2EB3C4AC001565E5 /* MPKitMock.swift in Sources */, diff --git a/mParticle-Apple-SDK/Include/mParticle.h b/mParticle-Apple-SDK/Include/mParticle.h index 344b18d96..e933ba69c 100644 --- a/mParticle-Apple-SDK/Include/mParticle.h +++ b/mParticle-Apple-SDK/Include/mParticle.h @@ -754,6 +754,8 @@ Defaults to false. Prevents the eventsHost above from overwriting the alias endp #endif /** + DEPRECATED: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:open:sourceapplication:annotation:) + Informs the mParticle SDK the app has been asked to open a resource identified by a URL. This method should be called only if proxiedAppDelegate is disabled. @param url The URL resource to open @@ -761,25 +763,45 @@ Defaults to false. Prevents the eventsHost above from overwriting the alias endp @param annotation A property list object supplied by the source app @see proxiedAppDelegate */ -- (void)openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(nullable id)annotation; +- (void)openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(nullable id)annotation DEPRECATED_MSG_ATTRIBUTE("iOS 27 will no longer support this protocol method"); /** + DEPRECATED: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:open:sourceapplication:annotation:) + Informs the mParticle SDK the app has been asked to open a resource identified by a URL. This method should be called only if proxiedAppDelegate is disabled. This method is only available for iOS 9 and above. @param url The URL resource to open @param options The dictionary of launch options @see proxiedAppDelegate */ -- (void)openURL:(NSURL *)url options:(nullable NSDictionary *)options; +- (void)openURL:(NSURL *)url options:(nullable NSDictionary *)options DEPRECATED_MSG_ATTRIBUTE("iOS 27 will no longer support this protocol method"); /** + DEPRECATED: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:continue:restorationhandler:) + Informs the mParticle SDK the app has been asked to open to continue an NSUserActivity. This method should be called only if proxiedAppDelegate is disabled. This method is only available for iOS 9 and above. @param userActivity The NSUserActivity that caused the app to be opened @param restorationHandler A block to execute if your app creates objects to perform the task. @see proxiedAppDelegate */ -- (BOOL)continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(void(^ _Nonnull)(NSArray> * __nullable restorableObjects))restorationHandler; +- (BOOL)continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(void(^ _Nonnull)(NSArray> * __nullable restorableObjects))restorationHandler DEPRECATED_MSG_ATTRIBUTE("iOS 27 will no longer support this protocol method"); + +/** + Informs the mParticle SDK the app has been asked to open a resource identified by a URL. + This method should be called only if proxiedAppDelegate is disabled. This method is only available for iOS 13 and above. + @param urlContext The UIOpenURLContext provided by the SceneDelegate + @see proxiedAppDelegate + */ +- (void)handleURLContext:(UIOpenURLContext *)urlContext NS_SWIFT_NAME(handleURLContext(_:)) API_AVAILABLE(ios(13.0)); + +/** + Informs the mParticle SDK the app has been asked to open to continue an NSUserActivity. + This method should be called only if proxiedAppDelegate is disabled. + @param userActivity The NSUserActivity that caused the app to be opened + @see proxiedAppDelegate + */ +- (void)handleUserActivity:(NSUserActivity *)userActivity NS_SWIFT_NAME(handleUserActivity(_:)); /** DEPRECATED: This method will permanently remove ALL MParticle data from the device, including MParticle UserDefaults and Database, it will also halt any further upload or download behavior that may be prepared diff --git a/mParticle-Apple-SDK/mParticle.m b/mParticle-Apple-SDK/mParticle.m index 1e1fd9d78..9998f4547 100644 --- a/mParticle-Apple-SDK/mParticle.m +++ b/mParticle-Apple-SDK/mParticle.m @@ -697,6 +697,46 @@ - (BOOL)continueUserActivity:(nonnull NSUserActivity *)userActivity restorationH return [self.appNotificationHandler continueUserActivity:userActivity restorationHandler:restorationHandler]; } +- (void)handleURLContext:(UIOpenURLContext *)urlContext API_AVAILABLE(ios(13.0)){ + NSString *messageURL = [NSString stringWithFormat:@"Opening URLContext URL: %@", urlContext.URL]; + [logger debug:messageURL]; + NSString *messageSource = [NSString stringWithFormat:@"Source: %@", urlContext.options.sourceApplication ? urlContext.options.sourceApplication : @"unknown"]; + [logger debug:messageSource]; + NSString *messageAnnotation = [NSString stringWithFormat:@"Annotation: %@", urlContext.options.annotation]; + [logger debug:messageAnnotation]; +#if TARGET_OS_IOS == 1 + if (@available(iOS 14.5, *)) { + NSString *messageEventAttribution = [NSString stringWithFormat:@"Event Attribution: %@", urlContext.options.eventAttribution]; + [logger debug:messageEventAttribution]; + } +#endif + NSString *messageOpenInPlace = [NSString stringWithFormat:@"Open in place: %@", urlContext.options.openInPlace ? @"True" : @"False"]; + [logger debug:messageOpenInPlace]; + + // Currently only one kit integration uses this dictionary for this key + // https://github.com/mparticle-integrations/mparticle-apple-integration-flurry/blob/a0856e271aa9a63a6668805582395dea63f96af5/mParticle-Flurry/MPKitFlurry.m#L148C38-L148C85 + NSDictionary *options = @{@"UIApplicationOpenURLOptionsSourceApplicationKey": urlContext.options.sourceApplication}; + + [[MParticle sharedInstance].appNotificationHandler openURL:urlContext.URL options:options]; +} + +- (void)handleUserActivity:(NSUserActivity *)userActivity { + NSString *message = [NSString stringWithFormat:@"User Activity Received"]; + [logger debug:message]; + NSString *messageType = [NSString stringWithFormat:@"User Activity Type: %@", userActivity.activityType]; + [logger debug:messageType]; + NSString *messageTitle = [NSString stringWithFormat:@"User Activity Title: %@", userActivity.title]; + [logger debug:messageTitle]; + NSString *messageUserInfo = [NSString stringWithFormat:@"User Activity User Info: %@", userActivity.userInfo]; + [logger debug:messageUserInfo]; + if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) { + NSString *messageURL = [NSString stringWithFormat:@"Opening UserActivity URL: %@", userActivity.webpageURL]; + [logger debug:messageURL]; + } + // When provided by the SceneDelegate NSUserActivity is not paired with a restorationHandler + [[MParticle sharedInstance].appNotificationHandler continueUserActivity: userActivity restorationHandler:^(NSArray> * _Nullable restorableObjects) {}]; +} + - (void)reset:(void (^)(void))completion { [executor executeOnMessage:^{ [self.kitContainer flushSerializedKits]; From 066f11f4fc44f90410a768e2b623719c733dbc92 Mon Sep 17 00:00:00 2001 From: Brandon Stalnaker Date: Mon, 17 Nov 2025 11:35:41 -0500 Subject: [PATCH 2/5] test fixes --- .../MParticleSceneDelegateTests.swift | 18 ++++-------------- mParticle-Apple-SDK.xcodeproj/project.pbxproj | 8 ++++++++ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift b/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift index 80039fa51..66cbf7946 100644 --- a/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift +++ b/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift @@ -41,6 +41,7 @@ final class MParticleSceneDelegateTests: MParticleTestBase { // MARK: - Method Existence Tests + @available(tvOS 13.0, *) func test_handleURLContext_methodExists() { // Verify the method exists with correct signature XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) @@ -51,19 +52,6 @@ final class MParticleSceneDelegateTests: MParticleTestBase { XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleUserActivity(_:)))) } - // MARK: - handleURLContext Tests - // Note: Testing with mock URLContext is complex due to system class limitations - // These tests focus on method availability and basic functionality - - func test_handleURLContext_availabilityCheck() { - // Verify the method is only available on iOS 13+ - if #available(iOS 13.0, *) { - XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) - } else { - XCTAssertFalse(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) - } - } - // MARK: - handleUserActivity Tests func test_handleUserActivity_invokesAppNotificationHandler() { @@ -117,7 +105,9 @@ final class MParticleSceneDelegateTests: MParticleTestBase { func test_bothMethods_exist() { // Verify both SceneDelegate support methods exist - XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) + if #available(tvOS 13.0, *) { + XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) + } XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleUserActivity(_:)))) } } diff --git a/mParticle-Apple-SDK.xcodeproj/project.pbxproj b/mParticle-Apple-SDK.xcodeproj/project.pbxproj index be89327c8..95a14fe2c 100644 --- a/mParticle-Apple-SDK.xcodeproj/project.pbxproj +++ b/mParticle-Apple-SDK.xcodeproj/project.pbxproj @@ -2368,6 +2368,7 @@ "$(inherited)", ); INFOPLIST_FILE = ./UnitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-Apple-SDK-NoLocationTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2380,6 +2381,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "./UnitTests/mParticle_iOS_SDKTests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 15.6; }; name = Debug; }; @@ -2396,6 +2398,7 @@ "$(inherited)", ); INFOPLIST_FILE = ./UnitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-Apple-SDK-NoLocationTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2408,6 +2411,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "./UnitTests/mParticle_iOS_SDKTests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 15.6; }; name = Release; }; @@ -2625,6 +2629,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = DLD43Y3TRP; INFOPLIST_FILE = ./UnitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-Apple-SDKTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2636,6 +2641,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "./UnitTests/mParticle_iOS_SDKTests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 15.6; }; name = Debug; }; @@ -2648,6 +2654,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = DLD43Y3TRP; INFOPLIST_FILE = ./UnitTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.mparticle.mParticle-Apple-SDKTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2659,6 +2666,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "./UnitTests/mParticle_iOS_SDKTests-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3"; + TVOS_DEPLOYMENT_TARGET = 15.6; }; name = Release; }; From e1ba42d050a29a446cce8d51398621904530bf92 Mon Sep 17 00:00:00 2001 From: Brandon Stalnaker Date: Mon, 17 Nov 2025 13:59:21 -0500 Subject: [PATCH 3/5] MPKitConfigurationTest fix --- UnitTests/ObjCTests/MPKitConfigurationTests.mm | 4 ++-- mParticle-Apple-SDK/Kits/MPKitConfiguration.mm | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/UnitTests/ObjCTests/MPKitConfigurationTests.mm b/UnitTests/ObjCTests/MPKitConfigurationTests.mm index 410e07351..bd1a69e67 100644 --- a/UnitTests/ObjCTests/MPKitConfigurationTests.mm +++ b/UnitTests/ObjCTests/MPKitConfigurationTests.mm @@ -81,7 +81,7 @@ - (void)tearDown { - (void)testInstance { XCTAssertNotNil(kitConfiguration); - XCTAssertEqualObjects(kitConfiguration.configurationHash, @(762651950)); + XCTAssertEqualObjects(kitConfiguration.configurationHash, @(969680750)); XCTAssertEqualObjects(kitConfiguration.integrationId, @37); XCTAssertEqualObjects(kitConfiguration.attributeValueFilteringHashedAttribute, @"12345"); XCTAssertEqualObjects(kitConfiguration.attributeValueFilteringHashedValue, @"54321"); @@ -183,7 +183,7 @@ - (void)testInvalidConfiguration { kitConfig = [[MPKitConfiguration alloc] initWithDictionary:configuration]; XCTAssertNotNil(kitConfig); - XCTAssertEqualObjects(kitConfig.configurationHash, @(1495473349)); + XCTAssertEqualObjects(kitConfig.configurationHash, @(-1872513399)); XCTAssertEqualObjects(kitConfig.integrationId, @80); XCTAssertNil(kitConfig.filters); diff --git a/mParticle-Apple-SDK/Kits/MPKitConfiguration.mm b/mParticle-Apple-SDK/Kits/MPKitConfiguration.mm index a3050d972..51b3dd7a5 100644 --- a/mParticle-Apple-SDK/Kits/MPKitConfiguration.mm +++ b/mParticle-Apple-SDK/Kits/MPKitConfiguration.mm @@ -23,7 +23,11 @@ - (instancetype)initWithDictionary:(NSDictionary *)configurationDictionary { return nil; } - NSData *ekConfigData = [NSJSONSerialization dataWithJSONObject:configurationDictionary options:0 error:nil]; + NSJSONWritingOptions options = 0; + if (@available(iOS 11.0, tvOS 11.0, *)) { + options = NSJSONWritingSortedKeys; + } + NSData *ekConfigData = [NSJSONSerialization dataWithJSONObject:configurationDictionary options:options error:nil]; NSString *ekConfigString = [[NSString alloc] initWithData:ekConfigData encoding:NSUTF8StringEncoding]; _configurationHash = @([[MPIHasher hashString:ekConfigString] intValue]); @@ -114,16 +118,11 @@ - (id)initWithCoder:(NSCoder *)coder { @try { configurationDictionary = [coder decodeObjectOfClass:[NSDictionary class] forKey:@"configurationDictionary"]; } - @catch ( NSException *e) { configurationDictionary = nil; MPILogError(@"Exception decoding MPKitConfiguration Attributes: %@", [e reason]); } - @finally { - self = [self initWithDictionary:configurationDictionary]; - } - self = [self initWithDictionary:configurationDictionary]; if (!self) { return nil; From 225013107b0d908645d205c8691493dc75086f68 Mon Sep 17 00:00:00 2001 From: Brandon Stalnaker Date: Wed, 19 Nov 2025 08:58:51 -0500 Subject: [PATCH 4/5] Address Dennis comments --- .../MParticleSceneDelegateTests.swift | 38 +------------------ mParticle-Apple-SDK/Include/mParticle.h | 3 ++ mParticle-Apple-SDK/mParticle.m | 4 +- 3 files changed, 6 insertions(+), 39 deletions(-) diff --git a/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift b/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift index 66cbf7946..9d74a62f7 100644 --- a/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift +++ b/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift @@ -1,9 +1,3 @@ -// -// MParticleSceneDelegateTests.swift -// mParticle-Apple-SDK -// -// Created by Brandon Stalnaker on 11/13/25. -// #if MPARTICLE_LOCATION_DISABLE import mParticle_Apple_SDK_NoLocation @@ -38,20 +32,7 @@ final class MParticleSceneDelegateTests: MParticleTestBase { appNotificationHandler.openURLWithOptionsURLParam = nil appNotificationHandler.openURLWithOptionsOptionsParam = nil } - - // MARK: - Method Existence Tests - - @available(tvOS 13.0, *) - func test_handleURLContext_methodExists() { - // Verify the method exists with correct signature - XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) - } - - func test_handleUserActivity_methodExists() { - // Verify the method exists with correct signature - XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleUserActivity(_:)))) - } - + // MARK: - handleUserActivity Tests func test_handleUserActivity_invokesAppNotificationHandler() { @@ -93,21 +74,4 @@ final class MParticleSceneDelegateTests: MParticleTestBase { XCTAssertNoThrow(restorationHandler?(nil)) XCTAssertNoThrow(restorationHandler?([])) } - - // MARK: - iOS Version Availability Tests - - func test_handleUserActivity_alwaysAvailable() { - // handleUserActivity should be available on all iOS versions - XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleUserActivity(_:)))) - } - - // MARK: - Integration Tests - - func test_bothMethods_exist() { - // Verify both SceneDelegate support methods exist - if #available(tvOS 13.0, *) { - XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleURLContext(_:)))) - } - XCTAssertTrue(mparticle.responds(to: #selector(mparticle.handleUserActivity(_:)))) - } } diff --git a/mParticle-Apple-SDK/Include/mParticle.h b/mParticle-Apple-SDK/Include/mParticle.h index e933ba69c..cd6685da5 100644 --- a/mParticle-Apple-SDK/Include/mParticle.h +++ b/mParticle-Apple-SDK/Include/mParticle.h @@ -755,6 +755,7 @@ Defaults to false. Prevents the eventsHost above from overwriting the alias endp /** DEPRECATED: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:open:sourceapplication:annotation:) + Use a UIScene lifecycle, mParticle's handleURLContext: method, and scene(_:openURLContexts:) from UISceneDelegate instead. Informs the mParticle SDK the app has been asked to open a resource identified by a URL. This method should be called only if proxiedAppDelegate is disabled. @@ -767,6 +768,7 @@ Defaults to false. Prevents the eventsHost above from overwriting the alias endp /** DEPRECATED: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:open:sourceapplication:annotation:) + Use a UIScene lifecycle, mParticle's handleURLContext: method, and scene(_:openURLContexts:) from UISceneDelegate instead. Informs the mParticle SDK the app has been asked to open a resource identified by a URL. This method should be called only if proxiedAppDelegate is disabled. This method is only available for iOS 9 and above. @@ -778,6 +780,7 @@ Defaults to false. Prevents the eventsHost above from overwriting the alias endp /** DEPRECATED: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/application(_:continue:restorationhandler:) + Use UIScene lifecycle, mParticle's handleUserActivity: method, and scene(_:continue:) from UISceneDelegate instead. Informs the mParticle SDK the app has been asked to open to continue an NSUserActivity. This method should be called only if proxiedAppDelegate is disabled. This method is only available for iOS 9 and above. diff --git a/mParticle-Apple-SDK/mParticle.m b/mParticle-Apple-SDK/mParticle.m index 9998f4547..232d6857d 100644 --- a/mParticle-Apple-SDK/mParticle.m +++ b/mParticle-Apple-SDK/mParticle.m @@ -717,7 +717,7 @@ - (void)handleURLContext:(UIOpenURLContext *)urlContext API_AVAILABLE(ios(13.0)) // https://github.com/mparticle-integrations/mparticle-apple-integration-flurry/blob/a0856e271aa9a63a6668805582395dea63f96af5/mParticle-Flurry/MPKitFlurry.m#L148C38-L148C85 NSDictionary *options = @{@"UIApplicationOpenURLOptionsSourceApplicationKey": urlContext.options.sourceApplication}; - [[MParticle sharedInstance].appNotificationHandler openURL:urlContext.URL options:options]; + [self.appNotificationHandler openURL:urlContext.URL options:options]; } - (void)handleUserActivity:(NSUserActivity *)userActivity { @@ -734,7 +734,7 @@ - (void)handleUserActivity:(NSUserActivity *)userActivity { [logger debug:messageURL]; } // When provided by the SceneDelegate NSUserActivity is not paired with a restorationHandler - [[MParticle sharedInstance].appNotificationHandler continueUserActivity: userActivity restorationHandler:^(NSArray> * _Nullable restorableObjects) {}]; + [self.appNotificationHandler continueUserActivity: userActivity restorationHandler:^(NSArray> * _Nullable restorableObjects) {}]; } - (void)reset:(void (^)(void))completion { From a7fc201573be88258dcb7d702d165f3464b98cc1 Mon Sep 17 00:00:00 2001 From: Brandon Stalnaker Date: Wed, 19 Nov 2025 15:54:01 -0500 Subject: [PATCH 5/5] final comment --- UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift b/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift index 9d74a62f7..4a36185f7 100644 --- a/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift +++ b/UnitTests/SwiftTests/MParticle/MParticleSceneDelegateTests.swift @@ -6,7 +6,6 @@ import mParticle_Apple_SDK #endif import XCTest -@available(iOS 13.0, *) final class MParticleSceneDelegateTests: MParticleTestBase { // MARK: - Properties