From e75e538ddbb1dad0baad02576df86f6b8eb1510f Mon Sep 17 00:00:00 2001 From: Jason Wray Date: Fri, 22 May 2015 19:15:46 -0700 Subject: Add location tests for iOS - Adds testDelegatesStartStopLocatingUser: Tests mapViewWillStartLocatingUser and mapViewDidStopLocatingUser delegate methods - Adds testUserTrackingModeFollow - Adds testUserTrackingModeFollowWithHeading (disabled because of library bug) Fakes user location via swizzled CLLocationManager and hard-coded mock coordinates and heading. --- test/ios/App-Info.plist | 2 + .../CLLocationManager+MockLocation.h | 39 +++++++ .../CLLocationManager+MockLocation.m | 81 +++++++++++++++ test/ios/LocationMocker/CSSwizzler.h | 22 ++++ test/ios/LocationMocker/CSSwizzler.m | 44 ++++++++ test/ios/LocationMocker/LocationMocker.h | 25 +++++ test/ios/LocationMocker/LocationMocker.m | 35 +++++++ test/ios/MapViewTests.m | 114 ++++++++++++++++++++- test/ios/ios-tests.xcodeproj/project.pbxproj | 26 +++++ .../xcshareddata/ios-tests.xccheckout | 2 +- .../xcschemes/Mapbox GL Tests.xcscheme | 3 + 11 files changed, 390 insertions(+), 3 deletions(-) create mode 100644 test/ios/LocationMocker/CLLocationManager+MockLocation.h create mode 100644 test/ios/LocationMocker/CLLocationManager+MockLocation.m create mode 100644 test/ios/LocationMocker/CSSwizzler.h create mode 100644 test/ios/LocationMocker/CSSwizzler.m create mode 100644 test/ios/LocationMocker/LocationMocker.h create mode 100644 test/ios/LocationMocker/LocationMocker.m diff --git a/test/ios/App-Info.plist b/test/ios/App-Info.plist index f5a44890e3..f817ea1d9a 100644 --- a/test/ios/App-Info.plist +++ b/test/ios/App-Info.plist @@ -40,6 +40,8 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + NSLocationAlwaysUsageDescription + Strictly for testing purposes, promise! UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/test/ios/LocationMocker/CLLocationManager+MockLocation.h b/test/ios/LocationMocker/CLLocationManager+MockLocation.h new file mode 100644 index 0000000000..48641d0291 --- /dev/null +++ b/test/ios/LocationMocker/CLLocationManager+MockLocation.h @@ -0,0 +1,39 @@ +// +// Based on gist by Eric Allam +// https://gist.github.com/ericallam/5689235 +// + +#import + +@interface CLLocationManager (MockLocation) + +- (void)custom_startUpdatingLocation; +- (CLLocation *)custom_location; + +- (void)custom_startUpdatingHeading; +- (CLHeading *)custom_heading; ++ (BOOL)custom_headingAvailable; + +@end + +// private setter struct for CLHeading +typedef struct { + double x; + double y; + double z; + double magneticHeading; + double trueHeading; + double accuracy; + double timestamp; + double temperature; + double magnitude; + double inclination; + int calibration; +} CLHeadingStruct; + +// create reference to private API method +@interface CLHeading () + +- (id)initWithClientHeading:(CLHeadingStruct)heading; + +@end diff --git a/test/ios/LocationMocker/CLLocationManager+MockLocation.m b/test/ios/LocationMocker/CLLocationManager+MockLocation.m new file mode 100644 index 0000000000..8937a5e0af --- /dev/null +++ b/test/ios/LocationMocker/CLLocationManager+MockLocation.m @@ -0,0 +1,81 @@ +// +// Based on gist by Eric Allam +// https://gist.github.com/ericallam/5689235 +// + +#import "CLLocationManager+MockLocation.h" +#import "LocationMocker.h" + +// This category implements methods that will be swizzled to replace key methods +// in CLLocationManager to mock out the user location. +@implementation CLLocationManager (MockLocation) + +// Replaces startUpdatingLocation, will send the locationManager:didUpdateLocations: message +// to the delegate after a wait of 0.1 seconds using dispatch_after to simulate how +// startUpdatingLocation actually works. This will not simulate any kind of location errors. +- (void)custom_startUpdatingLocation +{ + if (self.delegate) + { + if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) + { + // delay the locationManager:didUpdateLocations: message 0.1 seconds + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + NSArray *locations = @[self.location]; + [self.delegate locationManager:self didUpdateLocations:locations]; + }); + } + + if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateToLocation:fromLocation:)]) + { + // delay the locationManager:didUpdateToLocation:fromLocation: message 0.1 seconds + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + [self.delegate locationManager:self didUpdateToLocation:self.location fromLocation:nil]; + #pragma clang diagnostic pop + }); + } + } +} + +// Replaces location, returns the mocked CLLocation object +- (CLLocation *)custom_location; +{ + return [[CLLocation alloc] initWithLatitude:kMockedLatitude longitude:kMockedLongitude]; +} + + +// Replaces startUpdatingHeading, sends locationManager:didUpdateHeading: +- (void)custom_startUpdatingHeading +{ + if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateHeading:)]) + { + // delay the locationManager:didUpdateHeading: message 0.1 seconds + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + [self.delegate locationManager:self didUpdateHeading:self.heading]; + }); + } +} + +// Replaces heading, returns the mocked CLHeading object +- (CLHeading *)custom_heading; +{ + CLHeadingStruct heading; + heading.accuracy = kMockedHeadingAccuracy; + heading.trueHeading = kMockedHeadingTrueHeading; + heading.magneticHeading = kMockedHeadingMagneticHeading; + heading.x = kMockedHeadingX; + heading.y = kMockedHeadingY; + heading.z = kMockedHeadingZ; + + return [[CLHeading alloc] initWithClientHeading:heading]; +} + ++ (BOOL)custom_headingAvailable +{ + return YES; +} + +@end + diff --git a/test/ios/LocationMocker/CSSwizzler.h b/test/ios/LocationMocker/CSSwizzler.h new file mode 100644 index 0000000000..d0a2cc205c --- /dev/null +++ b/test/ios/LocationMocker/CSSwizzler.h @@ -0,0 +1,22 @@ +// +// Based on gist by Eric Allam +// https://gist.github.com/ericallam/5689235 +// + +#import + +@interface CSSwizzler : NSObject + ++ (void)swizzleClass:(id)cls + replaceClassMethod:(SEL)origMethodSelector + withMethod:(SEL)replacementMethodSelector; + ++ (void)swizzleClass:(id)cls + replaceMethod:(SEL)origMethodSelector + withMethod:(SEL)replacementMethodSelector; + ++ (void)swizzleClassOfInstance:(id)inst + replaceMethod:(SEL)origMethodSelector + withMethod:(SEL)replacementMethodSelector; + +@end diff --git a/test/ios/LocationMocker/CSSwizzler.m b/test/ios/LocationMocker/CSSwizzler.m new file mode 100644 index 0000000000..70c5b12bc1 --- /dev/null +++ b/test/ios/LocationMocker/CSSwizzler.m @@ -0,0 +1,44 @@ +// +// Based on gist by Eric Allam +// https://gist.github.com/ericallam/5689235 +// + +#import "CSSwizzler.h" +#import +#import + +@implementation CSSwizzler + ++ (void)swizzleClass:(id)cls + replaceClassMethod:(SEL)origMethodSelector + withMethod:(SEL)replacementMethodSelector; +{ + Method origMethod = nil, altMethod = nil; + origMethod = class_getClassMethod(cls, origMethodSelector); + altMethod = class_getClassMethod(cls, replacementMethodSelector); + method_exchangeImplementations(origMethod, altMethod); +} + ++ (void)swizzleClass:(id)cls + replaceMethod:(SEL)origMethodSelector + withMethod:(SEL)replacementMethodSelector; +{ + Method origMethod = nil, altMethod = nil; + origMethod = class_getInstanceMethod(cls, origMethodSelector); + altMethod = class_getInstanceMethod(cls, replacementMethodSelector); + method_exchangeImplementations(origMethod, altMethod); +} + ++ (void)swizzleClassOfInstance:(id)inst + replaceMethod:(SEL)origMethodSelector + withMethod:(SEL)replacementMethodSelector; +{ + const char *str = [[[inst class] description] UTF8String]; + Class cls = objc_getClass(str); + Method origMethod = nil, altMethod = nil; + origMethod = class_getInstanceMethod(cls, origMethodSelector); + altMethod = class_getInstanceMethod(cls, replacementMethodSelector); + method_exchangeImplementations(origMethod, altMethod); +} + +@end diff --git a/test/ios/LocationMocker/LocationMocker.h b/test/ios/LocationMocker/LocationMocker.h new file mode 100644 index 0000000000..14f1dea75f --- /dev/null +++ b/test/ios/LocationMocker/LocationMocker.h @@ -0,0 +1,25 @@ +// +// Based on gist by Eric Allam +// https://gist.github.com/ericallam/5689235 +// + +#import +#import + +// lat and long of the mocked current location (Mapbox San Francisco) +static const CLLocationDegrees kMockedLatitude = 37.775716; +static const CLLocationDegrees kMockedLongitude = -122.413688; + +// heading (values pulled from south-facing device) +static const double kMockedHeadingAccuracy = 20.0; +static const double kMockedHeadingTrueHeading = 170.53; +static const double kMockedHeadingMagneticHeading = 154.83; +static const double kMockedHeadingX = -7.079; +static const double kMockedHeadingY = -16.548; +static const double kMockedHeadingZ = -44.194; + +@interface LocationMocker : NSObject + ++ (void)load; + +@end diff --git a/test/ios/LocationMocker/LocationMocker.m b/test/ios/LocationMocker/LocationMocker.m new file mode 100644 index 0000000000..a32270413c --- /dev/null +++ b/test/ios/LocationMocker/LocationMocker.m @@ -0,0 +1,35 @@ +// +// Based on gist by Eric Allam +// https://gist.github.com/ericallam/5689235 +// + +#import "LocationMocker.h" +#import "CSSwizzler.h" +#import "CLLocationManager+MockLocation.h" + +@implementation LocationMocker + ++ (void)load +{ + [CSSwizzler swizzleClass:[CLLocationManager class] + replaceMethod:@selector(startUpdatingLocation) + withMethod:@selector(custom_startUpdatingLocation)]; + + [CSSwizzler swizzleClass:[CLLocationManager class] + replaceMethod:@selector(location) + withMethod:@selector(custom_location)]; + + [CSSwizzler swizzleClass:[CLLocationManager class] + replaceMethod:@selector(startUpdatingHeading) + withMethod:@selector(custom_startUpdatingHeading)]; + + [CSSwizzler swizzleClass:[CLLocationManager class] + replaceMethod:@selector(heading) + withMethod:@selector(custom_heading)]; + + [CSSwizzler swizzleClass:[CLLocationManager class] + replaceClassMethod:@selector(headingAvailable) + withMethod:@selector(custom_headingAvailable)]; +} + +@end diff --git a/test/ios/MapViewTests.m b/test/ios/MapViewTests.m index 05eafa381c..3755da0bcd 100644 --- a/test/ios/MapViewTests.m +++ b/test/ios/MapViewTests.m @@ -6,6 +6,15 @@ #import "MapboxGL.h" #import "MGLTViewController.h" +#import "LocationMocker/LocationMocker.h" +#import + +@interface MGLMapView (LocationManager) + +@property (nonatomic) CLLocationManager *locationManager; + +@end + @interface MapViewTests : KIFTestCase @end @@ -13,8 +22,6 @@ @implementation MapViewTests - (void)beforeEach { - [tester acknowledgeSystemAlert]; - [system simulateDeviceRotationToOrientation:UIDeviceOrientationPortrait]; [tester.viewController resetMapView]; @@ -443,4 +450,107 @@ userInfo:@{ @"animated" : @(animated) }]; } +- (void)testDelegatesStartStopLocatingUser { + NSNotification *notification = [system waitForNotificationName:@"mapViewWillStartLocatingUser" + object:tester.mapView + whileExecutingBlock:^{ + tester.mapView.showsUserLocation = YES; + }]; + + XCTAssertEqualObjects(notification.name, + @"mapViewWillStartLocatingUser", + @"mapViewWillStartLocatingUser delegate should receive message"); + XCTAssertNotNil(tester.mapView.locationManager, + "map view location manager should not be nil"); + + notification = [system waitForNotificationName:@"mapViewDidStopLocatingUser" + object:tester.mapView + whileExecutingBlock:^{ + tester.mapView.showsUserLocation = NO; + }]; + + XCTAssertEqualObjects(notification.name, + @"mapViewDidStopLocatingUser", + @"mapViewDidStopLocatingUser delegate should receive message"); + XCTAssertEqual(tester.mapView.userTrackingMode, + MGLUserTrackingModeNone, + @"user tracking mode should be none"); + XCTAssertNil(tester.mapView.locationManager, + "map view location manager should be nil"); +} + +- (void)mapViewWillStartLocatingUser:(MGLMapView *)mapView { + [[NSNotificationCenter defaultCenter] postNotificationName:@"mapViewWillStartLocatingUser" object:mapView]; +} + +- (void)mapViewDidStopLocatingUser:(MGLMapView *)mapView { + [[NSNotificationCenter defaultCenter] postNotificationName:@"mapViewDidStopLocatingUser" object:mapView]; +} + +- (void)testUserTrackingModeFollow { + tester.mapView.userTrackingMode = MGLUserTrackingModeFollow; + + [tester waitForTimeInterval:1]; + + XCTAssertEqual(tester.mapView.userLocationVisible, + YES, + @"user location should be visible"); + XCTAssertEqual(tester.mapView.userLocation.coordinate.latitude, + kMockedLatitude, + @"user location latitude should match mocked latitude"); + XCTAssertEqual(tester.mapView.userLocation.coordinate.longitude, + kMockedLongitude, + @"user location longitude should match mocked longitude"); + + [tester.mapView dragFromPoint:CGPointMake(10, 10) toPoint:CGPointMake(50, 100) steps:10]; + + XCTAssertEqual(tester.mapView.userLocationVisible, + YES, + @"user location should still be visible after panning"); + XCTAssertEqual(tester.mapView.userTrackingMode, + MGLUserTrackingModeNone, + @"user tracking mode should reset to none"); +} + +// DOES NOT CURRENTLY PASS, bug with tracking mode not being set properly (or reset) +- (void)testUserTrackingModeFollowWithHeading { + tester.mapView.userTrackingMode = MGLUserTrackingModeFollowWithHeading; + + [tester waitForTimeInterval:1]; + + XCTAssertEqual(tester.mapView.userLocationVisible, + YES, + @"user location should be visible"); + XCTAssertEqual(tester.mapView.userLocation.coordinate.latitude, + kMockedLatitude, + @"user location latitude should match mocked latitude"); + XCTAssertEqual(tester.mapView.userLocation.coordinate.longitude, + kMockedLongitude, + @"user location longitude should match mocked longitude"); + + XCTAssertEqual(tester.mapView.userTrackingMode, + MGLUserTrackingModeFollowWithHeading, + @"user tracking mode should be follow with heading"); + XCTAssertEqual(tester.mapView.userLocation.heading.trueHeading, + kMockedHeadingTrueHeading, + @"user true heading should match mocked true heading"); + XCTAssertEqual(tester.mapView.userLocation.heading.headingAccuracy, + kMockedHeadingAccuracy, + @"user heading accuracy should match mocked accuracy"); + + [tester.compass tap]; + + [tester waitForTimeInterval:1]; + + XCTAssertEqual(tester.mapView.userLocationVisible, + YES, + @"user location should be visible"); + XCTAssertEqual(tester.mapView.userTrackingMode, + MGLUserTrackingModeFollow, + @"user tracking mode should be follow"); + XCTAssertEqual(tester.mapView.direction, + 0, + @"user heading should be reset to zero/north"); +} + @end diff --git a/test/ios/ios-tests.xcodeproj/project.pbxproj b/test/ios/ios-tests.xcodeproj/project.pbxproj index cbe294a3eb..30494412e7 100644 --- a/test/ios/ios-tests.xcodeproj/project.pbxproj +++ b/test/ios/ios-tests.xcodeproj/project.pbxproj @@ -9,6 +9,9 @@ /* Begin PBXBuildFile section */ 96567A231B0E84CD00D78776 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 96567A221B0E84CD00D78776 /* LaunchScreen.xib */; }; 96567A311B0E8BB900D78776 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 96567A301B0E8BB900D78776 /* Images.xcassets */; }; + 96992E581B0FBB6F008978C0 /* CSSwizzler.m in Sources */ = {isa = PBXBuildFile; fileRef = 96992E541B0FBB6F008978C0 /* CSSwizzler.m */; }; + 96992E591B0FBB6F008978C0 /* LocationMocker.m in Sources */ = {isa = PBXBuildFile; fileRef = 96992E561B0FBB6F008978C0 /* LocationMocker.m */; }; + 96992E621B0FBC4F008978C0 /* CLLocationManager+MockLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 96992E611B0FBC4F008978C0 /* CLLocationManager+MockLocation.m */; }; DD043363196DBBD500E6F39D /* MGLTAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DD04335F196DBBD500E6F39D /* MGLTAppDelegate.m */; }; DD043364196DBBD500E6F39D /* MGLTViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DD043360196DBBD500E6F39D /* MGLTViewController.m */; }; DD043366196DBBE000E6F39D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DD043365196DBBE000E6F39D /* main.m */; }; @@ -85,6 +88,12 @@ /* Begin PBXFileReference section */ 96567A221B0E84CD00D78776 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = SOURCE_ROOT; }; 96567A301B0E8BB900D78776 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = SOURCE_ROOT; }; + 96992E531B0FBB6F008978C0 /* CSSwizzler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSwizzler.h; sourceTree = ""; }; + 96992E541B0FBB6F008978C0 /* CSSwizzler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSSwizzler.m; sourceTree = ""; }; + 96992E551B0FBB6F008978C0 /* LocationMocker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocationMocker.h; sourceTree = ""; }; + 96992E561B0FBB6F008978C0 /* LocationMocker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocationMocker.m; sourceTree = ""; }; + 96992E601B0FBC4F008978C0 /* CLLocationManager+MockLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CLLocationManager+MockLocation.h"; sourceTree = ""; }; + 96992E611B0FBC4F008978C0 /* CLLocationManager+MockLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CLLocationManager+MockLocation.m"; sourceTree = ""; }; DACAD7111B08719F009119DC /* MGLMapboxEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MGLMapboxEvents.h; path = ../../platform/ios/MGLMapboxEvents.h; sourceTree = SOURCE_ROOT; }; DD043323196DB9BC00E6F39D /* Mapbox GL Tests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Mapbox GL Tests.app"; sourceTree = BUILT_PRODUCTS_DIR; }; DD04335F196DBBD500E6F39D /* MGLTAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLTAppDelegate.m; sourceTree = SOURCE_ROOT; }; @@ -167,6 +176,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 96992E501B0FBB6F008978C0 /* LocationMocker */ = { + isa = PBXGroup; + children = ( + 96992E601B0FBC4F008978C0 /* CLLocationManager+MockLocation.h */, + 96992E611B0FBC4F008978C0 /* CLLocationManager+MockLocation.m */, + 96992E531B0FBB6F008978C0 /* CSSwizzler.h */, + 96992E541B0FBB6F008978C0 /* CSSwizzler.m */, + 96992E551B0FBB6F008978C0 /* LocationMocker.h */, + 96992E561B0FBB6F008978C0 /* LocationMocker.m */, + ); + path = LocationMocker; + sourceTree = SOURCE_ROOT; + }; DD04331A196DB9BC00E6F39D = { isa = PBXGroup; children = ( @@ -293,6 +315,7 @@ DD0E6F661B01806600DC035A /* MetricsTests.m */, DDBD0167196DC46B0033959E /* Supporting Files */, DDBD014D196DC3B00033959E /* KIF */, + 96992E501B0FBB6F008978C0 /* LocationMocker */, DD0E6F6B1B01906600DC035A /* OCMock */, DD0E6F861B01B67100DC035A /* OHHTTPStubs */, ); @@ -470,9 +493,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 96992E581B0FBB6F008978C0 /* CSSwizzler.m in Sources */, + 96992E591B0FBB6F008978C0 /* LocationMocker.m in Sources */, DD043366196DBBE000E6F39D /* main.m in Sources */, DD043363196DBBD500E6F39D /* MGLTAppDelegate.m in Sources */, DD043364196DBBD500E6F39D /* MGLTViewController.m in Sources */, + 96992E621B0FBC4F008978C0 /* CLLocationManager+MockLocation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout b/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout index b6ff646f18..33408b6083 100644 --- a/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout +++ b/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout @@ -24,7 +24,7 @@ 10265E242415D473A6A613214DB7AC3EE3D43F93 ../../../..test/ios/KIF 38C2A0D4F62B675E8C16C8BC1437C7753846C8AC - ../../../../test/ios/OHHTTPStubs/ + ../../../../test/ios/OHHTTPStubs 7E68CB584078A487C0535CC191D3B7551EEE2095 ../../../.. diff --git a/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme b/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme index 0008282f89..f7173ee644 100644 --- a/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme +++ b/test/ios/ios-tests.xcodeproj/xcshareddata/xcschemes/Mapbox GL Tests.xcscheme @@ -55,6 +55,9 @@ + + -- cgit v1.2.1