diff --git a/.gitignore b/.gitignore index 53505db84..e7f55eeae 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ Carthage # itself has no pod dependencies, only our tests do. Pods/ .clang-format +.idea diff --git a/Analytics.xcodeproj/project.pbxproj b/Analytics.xcodeproj/project.pbxproj index 724b881b8..0cdcb5611 100644 --- a/Analytics.xcodeproj/project.pbxproj +++ b/Analytics.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 067E6A3191007B3442DB29F0 /* Pods_AnalyticsTestsTVOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66058A0DB75544E7C2766C31 /* Pods_AnalyticsTestsTVOS.framework */; }; + 5AF0E8AE77F57B356DACCFE7 /* AutoScreenReportingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF0E8457CCFC077382BF449 /* AutoScreenReportingTest.swift */; }; + 630FC8EA2107F2A500A759C5 /* SEGScreenReporting.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AF0EDFEDC0EE79A0A0EB713 /* SEGScreenReporting.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6E265C791FB1178C0030E08E /* IntegrationsManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E265C781FB1178C0030E08E /* IntegrationsManagerTest.swift */; }; 6EEC1C712017EA370089C478 /* EndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EEC1C702017EA370089C478 /* EndToEndTests.swift */; }; 9D8CE59023EE014E00197D0C /* CryptoTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E91DECD335005322DA /* CryptoTest.swift */; }; @@ -27,6 +29,7 @@ 9D8CE59E23EE014E00197D0C /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADEB8E41DECD335005322DA /* TestUtils.swift */; }; 9D8CE5A023EE014E00197D0C /* Analytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EADEB85B1DECD080005322DA /* Analytics.framework */; }; A31958EF2385AC3A00A47EFA /* SerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A31958EE2385AC3A00A47EFA /* SerializationTests.m */; }; + A31CAFC224635F8400443BA4 /* AutoScreenReportingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF0E8457CCFC077382BF449 /* AutoScreenReportingTest.swift */; }; A352176023AD5825005B07F6 /* SEGMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = A352175F23AD5825005B07F6 /* SEGMacros.h */; settings = {ATTRIBUTES = (Private, ); }; }; E4ACAD51DB827114D8626C74 /* Pods_AnalyticsTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5238C0454BF56A36A536C5C1 /* Pods_AnalyticsTests.framework */; }; EA88A5981DED7608009FB66A /* SEGSerializableValue.h in Headers */ = {isa = PBXBuildFile; fileRef = EA88A5971DED7608009FB66A /* SEGSerializableValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -117,6 +120,9 @@ 03630CF57D6F100AC46A95FD /* Pods-AnalyticsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AnalyticsTests.release.xcconfig"; path = "Target Support Files/Pods-AnalyticsTests/Pods-AnalyticsTests.release.xcconfig"; sourceTree = ""; }; 4E5B177C4F6C1CAE4837FF7B /* Pods-AnalyticsTestsTVOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AnalyticsTestsTVOS.debug.xcconfig"; path = "Target Support Files/Pods-AnalyticsTestsTVOS/Pods-AnalyticsTestsTVOS.debug.xcconfig"; sourceTree = ""; }; 5238C0454BF56A36A536C5C1 /* Pods_AnalyticsTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AnalyticsTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5AF0E8457CCFC077382BF449 /* AutoScreenReportingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoScreenReportingTest.swift; sourceTree = ""; }; + 5AF0EDFEDC0EE79A0A0EB713 /* SEGScreenReporting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SEGScreenReporting.h; sourceTree = ""; }; + 63E090D722DD49C300DEC7EC /* UIViewController+SegScreenTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+SegScreenTest.h"; sourceTree = ""; }; 66058A0DB75544E7C2766C31 /* Pods_AnalyticsTestsTVOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AnalyticsTestsTVOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6E265C781FB1178C0030E08E /* IntegrationsManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationsManagerTest.swift; sourceTree = ""; }; 6EEC1C702017EA370089C478 /* EndToEndTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndToEndTests.swift; sourceTree = ""; }; @@ -312,6 +318,8 @@ 6E265C781FB1178C0030E08E /* IntegrationsManagerTest.swift */, 6EEC1C702017EA370089C478 /* EndToEndTests.swift */, A31958EE2385AC3A00A47EFA /* SerializationTests.m */, + 5AF0E8457CCFC077382BF449 /* AutoScreenReportingTest.swift */, + 63E090D722DD49C300DEC7EC /* UIViewController+SegScreenTest.h */, ); indentWidth = 2; path = AnalyticsTests; @@ -393,6 +401,7 @@ A352175F23AD5825005B07F6 /* SEGMacros.h */, EADEB8A21DECD12B005322DA /* UIViewController+SEGScreen.h */, EADEB8A31DECD12B005322DA /* UIViewController+SEGScreen.m */, + 5AF0EDFEDC0EE79A0A0EB713 /* SEGScreenReporting.h */, ); path = Internal; sourceTree = ""; @@ -461,6 +470,7 @@ EADEB8601DECD080005322DA /* Analytics.h in Headers */, EADEB8C31DECD12B005322DA /* SEGAnalyticsUtils.h in Headers */, EADEB8B91DECD12B005322DA /* SEGIntegrationsManager.h in Headers */, + 630FC8EA2107F2A500A759C5 /* SEGScreenReporting.h in Headers */, EADEB8AE1DECD12B005322DA /* SEGAES256Crypto.h in Headers */, EADEB8C51DECD12B005322DA /* SEGFileStorage.h in Headers */, EAA5427D1EB42B8C00945DA7 /* SEGReachability.h in Headers */, @@ -712,6 +722,7 @@ 9D8CE59823EE014E00197D0C /* AnalyticsUtilTests.swift in Sources */, 9D8CE59923EE014E00197D0C /* StoreKitTrackerTests.swift in Sources */, 9D8CE59A23EE014E00197D0C /* MiddlewareTests.swift in Sources */, + A31CAFC224635F8400443BA4 /* AutoScreenReportingTest.swift in Sources */, 9D8CE59B23EE014E00197D0C /* IntegrationsManagerTest.swift in Sources */, 9D8CE59C23EE014E00197D0C /* NSData+SEGGUNZIPP.m in Sources */, 9D8CE59D23EE014E00197D0C /* EndToEndTests.swift in Sources */, @@ -768,6 +779,7 @@ EADEB8EF1DECD335005322DA /* NSData+SEGGUNZIPP.m in Sources */, 6EEC1C712017EA370089C478 /* EndToEndTests.swift in Sources */, EADEB8F01DECD335005322DA /* TestUtils.swift in Sources */, + 5AF0E8AE77F57B356DACCFE7 /* AutoScreenReportingTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme b/Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme index 8c0862b7c..cce21eeec 100644 --- a/Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme +++ b/Analytics.xcodeproj/xcshareddata/xcschemes/Analytics.xcscheme @@ -20,20 +20,6 @@ ReferencedContainer = "container:Analytics.xcodeproj"> - - - - #import "SEGMiddleware.h" +/** + * Filenames of "Application Support" files where essential data is stored. + */ +extern NSString *_Nonnull const kSEGAnonymousIdFilename; +extern NSString *_Nonnull const kSEGCachedSettingsFilename; + /** * NSNotification name, that is posted after integrations are loaded. */ diff --git a/Analytics/Classes/Integrations/SEGIntegrationsManager.m b/Analytics/Classes/Integrations/SEGIntegrationsManager.m index 7fdd46340..07399aa58 100644 --- a/Analytics/Classes/Integrations/SEGIntegrationsManager.m +++ b/Analytics/Classes/Integrations/SEGIntegrationsManager.m @@ -26,9 +26,9 @@ #import "SEGAliasPayload.h" NSString *SEGAnalyticsIntegrationDidStart = @"io.segment.analytics.integration.did.start"; -static NSString *const SEGAnonymousIdKey = @"SEGAnonymousId"; -static NSString *const kSEGAnonymousIdFilename = @"segment.anonymousId"; -static NSString *const SEGCachedSettingsKey = @"analytics.settings.v2.plist"; +NSString *const SEGAnonymousIdKey = @"SEGAnonymousId"; +NSString *const kSEGAnonymousIdFilename = @"segment.anonymousId"; +NSString *const kSEGCachedSettingsFilename = @"analytics.settings.v2.plist"; @interface SEGIdentifyPayload (AnonymousId) @@ -357,9 +357,9 @@ - (NSDictionary *)cachedSettings { if (!_cachedSettings) { #if TARGET_OS_TV - _cachedSettings = [self.userDefaultsStorage dictionaryForKey:SEGCachedSettingsKey] ?: @{}; + _cachedSettings = [self.userDefaultsStorage dictionaryForKey:kSEGCachedSettingsFilename] ?: @{}; #else - _cachedSettings = [self.fileStorage dictionaryForKey:SEGCachedSettingsKey] ?: @{}; + _cachedSettings = [self.fileStorage dictionaryForKey:kSEGCachedSettingsFilename] ?: @{}; #endif } @@ -375,9 +375,9 @@ - (void)setCachedSettings:(NSDictionary *)settings } #if TARGET_OS_TV - [self.userDefaultsStorage setDictionary:_cachedSettings forKey:SEGCachedSettingsKey]; + [self.userDefaultsStorage setDictionary:_cachedSettings forKey:kSEGCachedSettingsFilename]; #else - [self.fileStorage setDictionary:_cachedSettings forKey:SEGCachedSettingsKey]; + [self.fileStorage setDictionary:_cachedSettings forKey:kSEGCachedSettingsFilename]; #endif [self updateIntegrationsWithSettings:settings[@"integrations"]]; diff --git a/Analytics/Classes/Internal/SEGAnalyticsUtils.h b/Analytics/Classes/Internal/SEGAnalyticsUtils.h index 82b8aa8e2..6214a9a3a 100644 --- a/Analytics/Classes/Internal/SEGAnalyticsUtils.h +++ b/Analytics/Classes/Internal/SEGAnalyticsUtils.h @@ -5,9 +5,6 @@ NS_ASSUME_NONNULL_BEGIN NSString *GenerateUUIDString(void); -// Validation Utils -BOOL serializableDictionaryTypes(NSDictionary *dict); - // Date Utils NSString *iso8601FormattedString(NSDate *date); NSString *iso8601NanoFormattedString(NSDate *date); diff --git a/Analytics/Classes/Internal/SEGFileStorage.m b/Analytics/Classes/Internal/SEGFileStorage.m index c25594999..ed534dbab 100644 --- a/Analytics/Classes/Internal/SEGFileStorage.m +++ b/Analytics/Classes/Internal/SEGFileStorage.m @@ -109,10 +109,18 @@ - (void)setArray:(nullable NSArray *)array forKey:(NSString *)key - (nullable NSString *)stringForKey:(NSString *)key { - NSDictionary *data = [self jsonForKey:key]; - if (data) { + id data = [self jsonForKey:key]; + + if (data == nil) { + return nil; + } + + if ([data isKindOfClass:[NSString class]]) { + return data; + } else if ([data isKindOfClass:[NSDictionary class]]) { return data[key]; } + return nil; } diff --git a/Analytics/Classes/Internal/SEGScreenReporting.h b/Analytics/Classes/Internal/SEGScreenReporting.h new file mode 100644 index 000000000..f40a05887 --- /dev/null +++ b/Analytics/Classes/Internal/SEGScreenReporting.h @@ -0,0 +1,17 @@ +#import +#import "SEGSerializableValue.h" + +/** Implement this protocol to override automatic screen reporting + */ + +NS_ASSUME_NONNULL_BEGIN + +@protocol SEGScreenReporting +@optional +-(void) seg_trackScreen:(UIViewController*)screen name:(NSString*)name; +@property (readonly, nullable) UIViewController *seg_mainViewController; +@end + +NS_ASSUME_NONNULL_END + + diff --git a/Analytics/Classes/Internal/SEGSegmentIntegration.h b/Analytics/Classes/Internal/SEGSegmentIntegration.h index 63942277c..c32e01d70 100644 --- a/Analytics/Classes/Internal/SEGSegmentIntegration.h +++ b/Analytics/Classes/Internal/SEGSegmentIntegration.h @@ -9,6 +9,13 @@ extern NSString *const SEGSegmentDidSendRequestNotification; extern NSString *const SEGSegmentRequestDidSucceedNotification; extern NSString *const SEGSegmentRequestDidFailNotification; +/** + * Filenames of "Application Support" files where essential data is stored. + */ +extern NSString *const kSEGUserIdFilename; +extern NSString *const kSEGQueueFilename; +extern NSString *const kSEGTraitsFilename; + @interface SEGSegmentIntegration : NSObject diff --git a/Analytics/Classes/Internal/UIViewController+SEGScreen.h b/Analytics/Classes/Internal/UIViewController+SEGScreen.h index 4d762f08c..8be5cb080 100644 --- a/Analytics/Classes/Internal/UIViewController+SEGScreen.h +++ b/Analytics/Classes/Internal/UIViewController+SEGScreen.h @@ -1,5 +1,5 @@ #import - +#import "SEGSerializableValue.h" @interface UIViewController (SEGScreen) @@ -7,3 +7,4 @@ + (UIViewController *)seg_topViewController; @end + diff --git a/Analytics/Classes/Internal/UIViewController+SEGScreen.m b/Analytics/Classes/Internal/UIViewController+SEGScreen.m index 8aaaa5861..4b1a7847f 100644 --- a/Analytics/Classes/Internal/UIViewController+SEGScreen.m +++ b/Analytics/Classes/Internal/UIViewController+SEGScreen.m @@ -2,6 +2,7 @@ #import #import "SEGAnalytics.h" #import "SEGAnalyticsUtils.h" +#import "SEGScreenReporting.h" @implementation UIViewController (SEGScreen) @@ -43,18 +44,48 @@ + (UIViewController *)seg_topViewController } + (UIViewController *)seg_topViewController:(UIViewController *)rootViewController +{ + UIViewController *nextRootViewController = [self seg_nextRootViewController:rootViewController]; + if (nextRootViewController) { + return [self seg_topViewController:nextRootViewController]; + } + + return rootViewController; +} + ++ (UIViewController *)seg_nextRootViewController:(UIViewController *)rootViewController { UIViewController *presentedViewController = rootViewController.presentedViewController; if (presentedViewController != nil) { - return [self seg_topViewController:presentedViewController]; + return presentedViewController; } if ([rootViewController isKindOfClass:[UINavigationController class]]) { - UIViewController *lastViewController = [[(UINavigationController *)rootViewController viewControllers] lastObject]; - return [self seg_topViewController:lastViewController]; + UIViewController *lastViewController = ((UINavigationController *)rootViewController).viewControllers.lastObject; + return lastViewController; } - return rootViewController; + if ([rootViewController isKindOfClass:[UITabBarController class]]) { + __auto_type *currentTabViewController = ((UITabBarController*)rootViewController).selectedViewController; + if (currentTabViewController != nil) { + return currentTabViewController; + } + } + + if (rootViewController.childViewControllers.count > 0) { + if ([rootViewController conformsToProtocol:@protocol(SEGScreenReporting)] && [rootViewController respondsToSelector:@selector(seg_mainViewController)]) { + __auto_type screenReporting = (UIViewController*)rootViewController; + return screenReporting.seg_mainViewController; + } + + // fall back on first child UIViewController as a "best guess" assumption + __auto_type *firstChildViewController = rootViewController.childViewControllers.firstObject; + if (firstChildViewController != nil) { + return firstChildViewController; + } + } + + return nil; } - (void)seg_viewDidAppear:(BOOL)animated @@ -74,6 +105,13 @@ - (void)seg_viewDidAppear:(BOOL)animated name = @"Unknown"; } } + + if ([top conformsToProtocol:@protocol(SEGScreenReporting)] && [top respondsToSelector:@selector(seg_trackScreen:name:)]) { + __auto_type screenReporting = (UIViewController*)top; + [screenReporting seg_trackScreen:top name:name]; + return; + } + [[SEGAnalytics sharedAnalytics] screen:name properties:nil options:nil]; [self seg_viewDidAppear:animated]; diff --git a/Analytics/Classes/SEGAnalytics.m b/Analytics/Classes/SEGAnalytics.m index d5cc92134..9159fe7bb 100644 --- a/Analytics/Classes/SEGAnalytics.m +++ b/Analytics/Classes/SEGAnalytics.m @@ -169,8 +169,12 @@ - (void)_applicationWillEnterForeground if (!self.configuration.trackApplicationLifecycleEvents) { return; } + NSString *currentVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; + NSString *currentBuild = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; [self track:@"Application Opened" properties:@{ @"from_background" : @YES, + @"version" : currentVersion ?: @"", + @"build" : currentBuild ?: @"", }]; } diff --git a/Analytics/Classes/SEGAnalyticsConfiguration.m b/Analytics/Classes/SEGAnalyticsConfiguration.m index 46d712351..b8b79806c 100644 --- a/Analytics/Classes/SEGAnalyticsConfiguration.m +++ b/Analytics/Classes/SEGAnalyticsConfiguration.m @@ -68,10 +68,7 @@ - (instancetype)init _factories = [NSMutableArray array]; Class applicationClass = NSClassFromString(@"UIApplication"); if (applicationClass) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - _application = [applicationClass performSelector:NSSelectorFromString(@"sharedApplication")]; -#pragma clang diagnostic pop + _application = [applicationClass performSelector:@selector(sharedApplication)]; } } return self; diff --git a/AnalyticsTests/AnalyticsTests-Bridging-Header.h b/AnalyticsTests/AnalyticsTests-Bridging-Header.h index 877104033..07afba377 100644 --- a/AnalyticsTests/AnalyticsTests-Bridging-Header.h +++ b/AnalyticsTests/AnalyticsTests-Bridging-Header.h @@ -12,7 +12,10 @@ #import #import #import +#import #import "NSData+SEGGUNZIPP.h" // Temp hack. We should fix the LSNocilla podspec to make this header publicly available #import "LSMatcher.h" + +#import "UIViewController+SegScreenTest.h" diff --git a/AnalyticsTests/AutoScreenReportingTest.swift b/AnalyticsTests/AutoScreenReportingTest.swift new file mode 100644 index 000000000..2bb715f87 --- /dev/null +++ b/AnalyticsTests/AutoScreenReportingTest.swift @@ -0,0 +1,136 @@ +// +// Created by David Whetstone on 2018-11-04. +// Copyright (c) 2018 Segment. All rights reserved. +// + +import Foundation +import Quick +import Nimble +import SwiftTryCatch +@testable import Analytics + +class AutoScreenReportingTests: QuickSpec { + + override func spec() { + + var window: UIWindow! + var rootVC: UIViewController! + + beforeEach { + let config = SEGAnalyticsConfiguration(writeKey: "foobar") + config.trackApplicationLifecycleEvents = true + config.recordScreenViews = true + + window = UIWindow() + rootVC = UIViewController() + window.addSubview(rootVC.view) + } + + + describe("given a single view controller") { + + it("seg_topViewController returns that view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === rootVC + } + } + + describe("given a presented view controller") { + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + rootVC.present(expectedVC, animated: false) + } + + it("seg_topViewController returns the presented view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + + describe("given a pushed view controller") { + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + let nc = UINavigationController() + rootVC.present(nc, animated: false) + nc.pushViewController(expectedVC, animated: false) + } + + it("seg_topViewController returns the pushed view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + + describe("given a child of a UITabBarController") { + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + let tabBarController = UITabBarController() + rootVC.present(tabBarController, animated: false) + tabBarController.viewControllers = [UIViewController(), expectedVC, UIViewController()] + tabBarController.selectedIndex = 1 + } + + it("seg_topViewController returns the currently selected view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + + describe("given a child of a custom container view controller conforming to SEGScreenReporting") { + + class CustomContainerViewController: UIViewController, SEGScreenReporting { + var selectedIndex: Int = 0 + var seg_mainViewController: UIViewController? { + return childViewControllers[selectedIndex] + } + } + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + let containerVC = CustomContainerViewController() + rootVC.present(containerVC, animated: false) + [UIViewController(), expectedVC, UIViewController()].forEach { child in + containerVC.addChildViewController(child) + } + containerVC.selectedIndex = 1 + } + + it("seg_topViewController returns the currently selected view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + + describe("given a child of a container view controller NOT conforming to SEGScreenReporting") { + + var expectedVC: UIViewController! + + beforeEach { + expectedVC = UIViewController() + let containerVC = UIViewController() + rootVC.present(containerVC, animated: false) + [expectedVC, UIViewController(), UIViewController()].forEach { child in + containerVC.addChildViewController(child) + } + } + + it("seg_topViewController returns the first child view controller") { + let actualVC = UIViewController.seg_topViewController(rootVC) + expect(actualVC) === expectedVC + } + } + } +} + + diff --git a/AnalyticsTests/UIViewController+SegScreenTest.h b/AnalyticsTests/UIViewController+SegScreenTest.h new file mode 100644 index 000000000..9e63ade3c --- /dev/null +++ b/AnalyticsTests/UIViewController+SegScreenTest.h @@ -0,0 +1,21 @@ +// +// UIViewController+SegScreenTest.h +// Analytics +// +// Created by David Whetstone on 7/15/19. +// Copyright © 2019 Segment. All rights reserved. +// + +#ifndef UIViewController_SegScreenTest_h +#define UIViewController_SegScreenTest_h + + +@interface UIViewController (Testing) +/// We need to expose this normally private method to tests, as the public facing +/// `+ (UIViewController *)seg_topViewController` relies on the `application` property +/// of `SEGAnalyticsConfiguration`, which won't be set in these tests. ++ (UIViewController *)seg_topViewController:(UIViewController *)rootViewController; +@end + + +#endif /* UIViewController_SegScreenTest_h */ diff --git a/AnalyticsTests/Utils/TestUtils.swift b/AnalyticsTests/Utils/TestUtils.swift index c15490c47..2752b803e 100644 --- a/AnalyticsTests/Utils/TestUtils.swift +++ b/AnalyticsTests/Utils/TestUtils.swift @@ -85,7 +85,7 @@ class JsonGzippedBody : LSMatcher, LSMatcheable { } func matchesJson(_ json: AnyObject) -> Bool { - let actualValue : () -> NSObject! = { + let actualValue : () -> NSObject = { return json as! NSObject } let failureMessage = FailureMessage() diff --git a/Podfile.lock b/Podfile.lock index 1655d56a2..695811b0e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -42,4 +42,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: bb5abec39a698f101923cddb294d2dfb1b19f105 -COCOAPODS: 1.8.4 +COCOAPODS: 1.9.1