@@ -1052,4 +1052,246 @@ final class Analytics_Tests: XCTestCase {
10521052 let trackEvent2 : TrackEvent ? = outputReader. lastEvent as? TrackEvent
10531053 XCTAssertEqual ( trackEvent2? . context? . value ( forKeyPath: " __eventOrigin.type " ) , " mobile " )
10541054 }
1055+
1056+ func testUserInfoSubscription( ) {
1057+ Storage . hardSettingsReset ( writeKey: " test " )
1058+ let analytics = Analytics ( configuration: Configuration ( writeKey: " test " ) )
1059+
1060+ waitUntilStarted ( analytics: analytics)
1061+
1062+ var callCount = 0
1063+ var capturedUserInfo : UserInfo ?
1064+
1065+ let initialExpectation = XCTestExpectation ( description: " Initial state received " )
1066+ let identifyExpectation = XCTestExpectation ( description: " Identify update received " )
1067+
1068+ // Subscribe and verify we get initial state immediately
1069+ let subscriptionId = analytics. subscribeToUserInfo { userInfo in
1070+ callCount += 1
1071+ capturedUserInfo = userInfo
1072+
1073+ if callCount == 1 {
1074+ initialExpectation. fulfill ( )
1075+ } else if callCount == 2 {
1076+ identifyExpectation. fulfill ( )
1077+ }
1078+ }
1079+
1080+ // Wait for initial callback
1081+ wait ( for: [ initialExpectation] , timeout: 2.0 )
1082+
1083+ XCTAssertEqual ( 1 , callCount)
1084+ XCTAssertNotNil ( capturedUserInfo)
1085+ XCTAssertNotNil ( capturedUserInfo? . anonymousId)
1086+ XCTAssertNil ( capturedUserInfo? . userId)
1087+
1088+ let initialAnonId = analytics. anonymousId
1089+ XCTAssertEqual ( initialAnonId, capturedUserInfo? . anonymousId)
1090+
1091+ // Update user info and verify handler is called again
1092+ analytics
. identify ( userId
: " brandon " , traits
: MyTraits ( email
: " [email protected] " ) ) 1093+
1094+ wait ( for: [ identifyExpectation] , timeout: 2.0 )
1095+
1096+ XCTAssertEqual ( 2 , callCount)
1097+ XCTAssertEqual ( " brandon " , capturedUserInfo? . userId)
1098+ XCTAssertEqual ( " brandon " , analytics. userId)
1099+
1100+ let traits : MyTraits ? = analytics. traits ( )
1101+ XCTAssertEqual ( " [email protected] " , traits
? . email
) 1102+
1103+ // Unsubscribe and verify handler stops firing
1104+ analytics. unsubscribe ( subscriptionId)
1105+
1106+ let oldCallCount = callCount
1107+ analytics. identify ( userId: " different_user " )
1108+
1109+ // Give it a moment to potentially fire (it shouldn't)
1110+ let noCallExpectation = XCTestExpectation ( description: " Should not be called " )
1111+ noCallExpectation. isInverted = true
1112+
1113+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.5 ) {
1114+ if callCount > oldCallCount {
1115+ noCallExpectation. fulfill ( )
1116+ }
1117+ }
1118+
1119+ wait ( for: [ noCallExpectation] , timeout: 1.0 )
1120+ XCTAssertEqual ( oldCallCount, callCount)
1121+ XCTAssertEqual ( " brandon " , capturedUserInfo? . userId) // Still has old value
1122+ }
1123+
1124+ func testUserInfoSubscriptionWithReset( ) {
1125+ Storage . hardSettingsReset ( writeKey: " test " )
1126+ let analytics = Analytics ( configuration: Configuration ( writeKey: " test " ) )
1127+
1128+ waitUntilStarted ( analytics: analytics)
1129+
1130+ var callCount = 0
1131+ var capturedUserInfo : UserInfo ?
1132+
1133+ let initialExpectation = XCTestExpectation ( description: " Initial " )
1134+ let identifyExpectation = XCTestExpectation ( description: " Identify " )
1135+ let resetExpectation = XCTestExpectation ( description: " Reset " )
1136+
1137+ analytics. subscribeToUserInfo { userInfo in
1138+ callCount += 1
1139+ capturedUserInfo = userInfo
1140+
1141+ if callCount == 1 {
1142+ initialExpectation. fulfill ( )
1143+ } else if callCount == 2 {
1144+ identifyExpectation. fulfill ( )
1145+ } else if callCount == 3 {
1146+ resetExpectation. fulfill ( )
1147+ }
1148+ }
1149+
1150+ wait ( for: [ initialExpectation] , timeout: 2.0 )
1151+
1152+ let originalAnonId = capturedUserInfo? . anonymousId
1153+ XCTAssertEqual ( 1 , callCount)
1154+
1155+ // Set some user data
1156+ analytics
. identify ( userId
: " brandon " , traits
: MyTraits ( email
: " [email protected] " ) ) 1157+ wait ( for: [ identifyExpectation] , timeout: 2.0 )
1158+
1159+ XCTAssertEqual ( 2 , callCount)
1160+ XCTAssertEqual ( " brandon " , capturedUserInfo? . userId)
1161+
1162+ // Reset and verify handler is called with cleared data
1163+ analytics. reset ( )
1164+ wait ( for: [ resetExpectation] , timeout: 2.0 )
1165+
1166+ XCTAssertEqual ( 3 , callCount)
1167+ XCTAssertNil ( capturedUserInfo? . userId)
1168+ XCTAssertNil ( capturedUserInfo? . referrer)
1169+ XCTAssertNotEqual ( originalAnonId, capturedUserInfo? . anonymousId)
1170+
1171+ // Check analytics state AFTER waiting for callback
1172+ let traitsDict : [ String : Any ] ? = analytics. traits ( )
1173+ XCTAssertEqual ( traitsDict? . count, 0 )
1174+ }
1175+
1176+ func testUserInfoSubscriptionWithReferrer( ) {
1177+ Storage . hardSettingsReset ( writeKey: " test " )
1178+ let analytics = Analytics ( configuration: Configuration ( writeKey: " test " ) )
1179+
1180+ waitUntilStarted ( analytics: analytics)
1181+
1182+ var callCount = 0
1183+ var capturedUserInfo : UserInfo ?
1184+
1185+ let initialExpectation = XCTestExpectation ( description: " Initial " )
1186+ let referrerExpectation = XCTestExpectation ( description: " Referrer " )
1187+
1188+ analytics. subscribeToUserInfo { userInfo in
1189+ callCount += 1
1190+ capturedUserInfo = userInfo
1191+
1192+ if callCount == 1 {
1193+ initialExpectation. fulfill ( )
1194+ } else if callCount == 2 {
1195+ referrerExpectation. fulfill ( )
1196+ }
1197+ }
1198+
1199+ wait ( for: [ initialExpectation] , timeout: 2.0 )
1200+
1201+ XCTAssertEqual ( 1 , callCount)
1202+ XCTAssertNil ( capturedUserInfo? . referrer)
1203+
1204+ // Set a referrer
1205+ analytics. openURL ( URL ( string: " https://google.com " ) !)
1206+ wait ( for: [ referrerExpectation] , timeout: 2.0 )
1207+
1208+ XCTAssertEqual ( 2 , callCount)
1209+ XCTAssertEqual ( " https://google.com " , capturedUserInfo? . referrer? . absoluteString)
1210+ }
1211+
1212+ func testMultipleUserInfoSubscriptions( ) {
1213+ Storage . hardSettingsReset ( writeKey: " test " )
1214+ let analytics = Analytics ( configuration: Configuration ( writeKey: " test " ) )
1215+
1216+ waitUntilStarted ( analytics: analytics)
1217+
1218+ var firstCallCount = 0
1219+ var secondCallCount = 0
1220+
1221+ let initialExpectation = XCTestExpectation ( description: " Initial callbacks " )
1222+ initialExpectation. expectedFulfillmentCount = 2 // Both subscriptions
1223+
1224+ let identifyExpectation = XCTestExpectation ( description: " Identify callbacks " )
1225+ identifyExpectation. expectedFulfillmentCount = 2 // Both subscriptions
1226+
1227+ // Create two subscriptions
1228+ analytics. subscribeToUserInfo { _ in
1229+ firstCallCount += 1
1230+ if firstCallCount == 1 {
1231+ initialExpectation. fulfill ( )
1232+ } else if firstCallCount == 2 {
1233+ identifyExpectation. fulfill ( )
1234+ }
1235+ }
1236+
1237+ analytics. subscribeToUserInfo { _ in
1238+ secondCallCount += 1
1239+ if secondCallCount == 1 {
1240+ initialExpectation. fulfill ( )
1241+ } else if secondCallCount == 2 {
1242+ identifyExpectation. fulfill ( )
1243+ }
1244+ }
1245+
1246+ // Both should be called for initial state
1247+ wait ( for: [ initialExpectation] , timeout: 2.0 )
1248+ XCTAssertEqual ( 1 , firstCallCount)
1249+ XCTAssertEqual ( 1 , secondCallCount)
1250+
1251+ // Both should fire when state changes
1252+ analytics. identify ( userId: " brandon " )
1253+ wait ( for: [ identifyExpectation] , timeout: 2.0 )
1254+
1255+ XCTAssertEqual ( 2 , firstCallCount)
1256+ XCTAssertEqual ( 2 , secondCallCount)
1257+ }
1258+
1259+ func testUserInfoSubscriptionCalledOnMainQueue( ) {
1260+ Storage . hardSettingsReset ( writeKey: " test " )
1261+ let analytics = Analytics ( configuration: Configuration ( writeKey: " test " ) )
1262+
1263+ waitUntilStarted ( analytics: analytics)
1264+
1265+ let expectation = XCTestExpectation ( description: " Handler called on main queue " )
1266+ expectation. expectedFulfillmentCount = 2 // Initial + identify
1267+
1268+ analytics. subscribeToUserInfo { userInfo in
1269+ XCTAssertTrue ( Thread . isMainThread, " Handler should be called on main thread " )
1270+ expectation. fulfill ( )
1271+ }
1272+
1273+ analytics. identify ( userId: " brandon " )
1274+
1275+ wait ( for: [ expectation] , timeout: 2.0 )
1276+ }
1277+
1278+ func testUnsubscribeWithInvalidId( ) {
1279+ Storage . hardSettingsReset ( writeKey: " test " )
1280+ let analytics = Analytics ( configuration: Configuration ( writeKey: " test " ) )
1281+
1282+ waitUntilStarted ( analytics: analytics)
1283+
1284+ // Should not crash with invalid ID
1285+ analytics. unsubscribe ( 999999 )
1286+ analytics. unsubscribe ( - 1 )
1287+
1288+ // Should work fine after bogus unsubscribe calls
1289+ let expectation = XCTestExpectation ( description: " Subscription works after invalid unsubscribe " )
1290+
1291+ analytics. subscribeToUserInfo { _ in
1292+ expectation. fulfill ( )
1293+ }
1294+
1295+ wait ( for: [ expectation] , timeout: 2.0 )
1296+ }
10551297}
0 commit comments