diff --git a/platform/ios/app/Main.storyboard b/platform/ios/app/Main.storyboard index 1190070d8ea..fb9b0c6d394 100644 --- a/platform/ios/app/Main.storyboard +++ b/platform/ios/app/Main.storyboard @@ -41,6 +41,7 @@ + @@ -174,6 +175,9 @@ + + + diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index f97263f2cd8..20024c8749a 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ DA35A2CA1CCAAAD200E826B2 /* NSValue+MGLAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = DA35A2C71CCAAAD200E826B2 /* NSValue+MGLAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA35A2CB1CCAAAD200E826B2 /* NSValue+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2C81CCAAAD200E826B2 /* NSValue+MGLAdditions.m */; }; DA35A2CC1CCAAAD200E826B2 /* NSValue+MGLAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2C81CCAAAD200E826B2 /* NSValue+MGLAdditions.m */; }; + DA4A0DF01CCE82500045352A /* MGLAnnotationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA4A0DEF1CCE82500045352A /* MGLAnnotationTests.m */; }; DA821D061CCC6D59007508D4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA821D041CCC6D59007508D4 /* LaunchScreen.storyboard */; }; DA821D071CCC6D59007508D4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = DA821D051CCC6D59007508D4 /* Main.storyboard */; }; DA8847D91CBAF91600AB86E3 /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; }; @@ -253,6 +254,13 @@ remoteGlobalIDString = DA8847D11CBAF91600AB86E3; remoteInfo = dynamic; }; + DA4A0DF21CCE82500045352A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DA1DC9421CB6C1C2006E619F /* Project object */; + proxyType = 1; + remoteGlobalIDString = DA1DC9491CB6C1C2006E619F; + remoteInfo = iosapp; + }; DA8847D71CBAF91600AB86E3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = DA1DC9421CB6C1C2006E619F /* Project object */; @@ -344,6 +352,9 @@ DA35A2C71CCAAAD200E826B2 /* NSValue+MGLAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSValue+MGLAdditions.h"; sourceTree = ""; }; DA35A2C81CCAAAD200E826B2 /* NSValue+MGLAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSValue+MGLAdditions.m"; sourceTree = ""; }; DA35A2D11CCAB25200E826B2 /* jazzy.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = jazzy.yml; sourceTree = ""; }; + DA4A0DED1CCE82500045352A /* uitest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = uitest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DA4A0DEF1CCE82500045352A /* MGLAnnotationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLAnnotationTests.m; sourceTree = ""; }; + DA4A0DF11CCE82500045352A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; DA4A26961CB6E795000B7809 /* Mapbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Mapbox.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DA821D041CCC6D59007508D4 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; DA821D051CCC6D59007508D4 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; @@ -481,6 +492,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA4A0DEA1CCE82500045352A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DA8847CE1CBAF91600AB86E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -518,6 +536,7 @@ DABCABA91CB80692000A7C39 /* Benchmarking App */, DA8847D31CBAF91600AB86E3 /* SDK */, DA2E88521CC036F400F24E7B /* SDK Tests */, + DA4A0DEE1CCE82500045352A /* uitest */, DA1DC9921CB6DF24006E619F /* Frameworks */, DAC07C951CBB2CAD000CB309 /* Configuration */, DA1DC94B1CB6C1C2006E619F /* Products */, @@ -534,6 +553,7 @@ DA2E88511CC036F400F24E7B /* test.xctest */, DA8933D51CCD306400E68420 /* Mapbox.bundle */, DA25D5B91CCD9EDE00607828 /* Settings.bundle */, + DA4A0DED1CCE82500045352A /* uitest.xctest */, ); name = Products; sourceTree = ""; @@ -612,6 +632,15 @@ path = test; sourceTree = ""; }; + DA4A0DEE1CCE82500045352A /* uitest */ = { + isa = PBXGroup; + children = ( + DA4A0DEF1CCE82500045352A /* MGLAnnotationTests.m */, + DA4A0DF11CCE82500045352A /* Info.plist */, + ); + path = uitest; + sourceTree = ""; + }; DA8847D31CBAF91600AB86E3 /* SDK */ = { isa = PBXGroup; children = ( @@ -983,6 +1012,24 @@ productReference = DA2E88511CC036F400F24E7B /* test.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + DA4A0DEC1CCE82500045352A /* uitest */ = { + isa = PBXNativeTarget; + buildConfigurationList = DA4A0DF61CCE82500045352A /* Build configuration list for PBXNativeTarget "uitest" */; + buildPhases = ( + DA4A0DE91CCE82500045352A /* Sources */, + DA4A0DEA1CCE82500045352A /* Frameworks */, + DA4A0DEB1CCE82500045352A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DA4A0DF31CCE82500045352A /* PBXTargetDependency */, + ); + name = uitest; + productName = uitest; + productReference = DA4A0DED1CCE82500045352A /* uitest.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; DA8847D11CBAF91600AB86E3 /* dynamic */ = { isa = PBXNativeTarget; buildConfigurationList = DA8847DD1CBAF91600AB86E3 /* Build configuration list for PBXNativeTarget "dynamic" */; @@ -1075,6 +1122,10 @@ DA2E88501CC036F400F24E7B = { CreatedOnToolsVersion = 7.3; }; + DA4A0DEC1CCE82500045352A = { + CreatedOnToolsVersion = 7.3; + TestTargetID = DA1DC9491CB6C1C2006E619F; + }; DA8847D11CBAF91600AB86E3 = { CreatedOnToolsVersion = 7.3; }; @@ -1109,6 +1160,7 @@ DA8933D41CCD306400E68420 /* bundle */, DA25D5B81CCD9EDE00607828 /* settings */, DA2E88501CC036F400F24E7B /* test */, + DA4A0DEC1CCE82500045352A /* uitest */, ); }; /* End PBXProject section */ @@ -1145,6 +1197,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA4A0DEB1CCE82500045352A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DA8847D01CBAF91600AB86E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1235,6 +1294,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + DA4A0DE91CCE82500045352A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DA4A0DF01CCE82500045352A /* MGLAnnotationTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DA8847CD1CBAF91600AB86E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1343,6 +1410,11 @@ target = DA8847D11CBAF91600AB86E3 /* dynamic */; targetProxy = DA2E88571CC036F400F24E7B /* PBXContainerItemProxy */; }; + DA4A0DF31CCE82500045352A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DA1DC9491CB6C1C2006E619F /* iosapp */; + targetProxy = DA4A0DF21CCE82500045352A /* PBXContainerItemProxy */; + }; DA8847D81CBAF91600AB86E3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DA8847D11CBAF91600AB86E3 /* dynamic */; @@ -1567,6 +1639,32 @@ }; name = Release; }; + DA4A0DF41CCE82500045352A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + INFOPLIST_FILE = uitest/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.uitest; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_TARGET_NAME = iosapp; + }; + name = Debug; + }; + DA4A0DF51CCE82500045352A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + INFOPLIST_FILE = uitest/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.uitest; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_TARGET_NAME = iosapp; + }; + name = Release; + }; DA8847DB1CBAF91600AB86E3 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = DAC07C961CBB2CD6000CB309 /* mbgl.xcconfig */; @@ -1792,6 +1890,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + DA4A0DF61CCE82500045352A /* Build configuration list for PBXNativeTarget "uitest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DA4A0DF41CCE82500045352A /* Debug */, + DA4A0DF51CCE82500045352A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; DA8847DD1CBAF91600AB86E3 /* Build configuration list for PBXNativeTarget "dynamic" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/CI.xcscheme b/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/CI.xcscheme index 26982c0747d..6e2c2a5c790 100644 --- a/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/CI.xcscheme +++ b/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/CI.xcscheme @@ -66,6 +66,16 @@ ReferencedContainer = "container:ios.xcodeproj"> + + + + + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> diff --git a/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/iosapp.xcscheme b/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/iosapp.xcscheme index 064add0fea3..9f5e2355c32 100644 --- a/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/iosapp.xcscheme +++ b/platform/ios/ios.xcodeproj/xcshareddata/xcschemes/iosapp.xcscheme @@ -26,8 +26,19 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/platform/ios/uitest/MGLAnnotationTests.m b/platform/ios/uitest/MGLAnnotationTests.m new file mode 100644 index 00000000000..0420fd3b59a --- /dev/null +++ b/platform/ios/uitest/MGLAnnotationTests.m @@ -0,0 +1,120 @@ +#import + +@interface MGLAnnotationTests : XCTestCase + +@end + +@implementation MGLAnnotationTests + +- (void)setUp { + [super setUp]; + + self.continueAfterFailure = NO; + + [XCUIDevice sharedDevice].orientation = UIDeviceOrientationPortrait; + [[[XCUIApplication alloc] init] launch]; +} + +- (void)testDropPin { + XCUIElement *mapElement = [[XCUIApplication alloc] init].otherElements[@"MGLMapView"]; + + // Drop a pin. + XCUIElement *mapProxyElement = mapElement.buttons[@"MGLMapViewProxyAccessibilityElement"]; + XCTAssertFalse(mapElement.buttons[@"MGLMapViewProxyAccessibilityElement"].exists, @"Map proxy element should be absent until opening callout view."); + [self expectationForPredicate:[NSPredicate predicateWithFormat:@"exists == true"] evaluatedWithObject:mapProxyElement handler:nil]; + [mapElement pressForDuration:1.1]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + + // Inspect the callout view. + NSString *annotationTitle = @"Dropped Pin"; + XCUIElement *calloutTitleText = mapElement.staticTexts[annotationTitle]; + XCTAssertTrue(calloutTitleText.exists, @"Callout title should be present after opening callout."); + NSString *annotationSubtitle = @"17°0′56″ south, 0° west"; + XCUIElement *calloutSubtitleText = mapElement.staticTexts[annotationSubtitle]; + XCTAssertTrue(calloutSubtitleText.exists, @"Callout subtitle should be present after opening callout."); + XCUIElement *calloutLeftAccessoryButton = mapElement.buttons[@"Left"]; + XCTAssertTrue(calloutLeftAccessoryButton.exists, @"Callout left accessory button should be present after opening callout."); + XCUIElement *calloutRightAccessoryButton = mapElement.buttons[@"Right"]; + XCTAssertTrue(calloutRightAccessoryButton.exists, @"Callout right accessory button should be present after opening callout."); + + // Close the callout view by tapping on the map proxy element. Note that the map proxy element has a gaping hole in the middle to accommodate the callout view. + [self expectationForPredicate:[NSPredicate predicateWithFormat:@"exists == false"] evaluatedWithObject:mapProxyElement handler:nil]; + XCUICoordinate *coordinate = [[mapProxyElement coordinateWithNormalizedOffset:CGVectorMake(0, 0)] coordinateWithOffset:CGVectorMake(100, 100)]; + [coordinate tap]; + [self waitForExpectationsWithTimeout:2 handler:nil]; + + XCTAssertFalse(calloutTitleText.exists, @"Callout title should be absent after closing callout."); + XCTAssertFalse(calloutSubtitleText.exists, @"Callout subtitle should be absent after closing callout."); + XCTAssertFalse(calloutLeftAccessoryButton.exists, @"Callout left accessory button should be absent after closing callout."); + XCTAssertFalse(calloutRightAccessoryButton.exists, @"Callout right accessory button should be absent after closing callout."); + + // Inspect the annotation itself. + XCUIElement *annotation = mapElement.buttons[@"MGLMapViewAnnotation 0"]; + XCTAssertTrue(annotation.exists, @"Annotation accessibility element should be present after closing callout view."); + XCTAssertEqualObjects(annotation.label, annotationTitle, @"Annotation label should be title."); + XCTAssertEqualObjects(annotation.value, annotationSubtitle, @"Annotation value should be subtitle."); +} + +- (void)testFrameWithGestures { + XCUIElement *mapElement = [[XCUIApplication alloc] init].otherElements[@"MGLMapView"]; + + // Drop a pin. + XCUIElement *mapProxyElement = mapElement.buttons[@"MGLMapViewProxyAccessibilityElement"]; + [self expectationForPredicate:[NSPredicate predicateWithFormat:@"exists == true"] evaluatedWithObject:mapProxyElement handler:nil]; + [mapElement pressForDuration:1.1]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + + // Close the callout view. + [self expectationForPredicate:[NSPredicate predicateWithFormat:@"exists == false"] evaluatedWithObject:mapProxyElement handler:nil]; + XCUICoordinate *coordinate = [[mapProxyElement coordinateWithNormalizedOffset:CGVectorMake(0, 0)] coordinateWithOffset:CGVectorMake(100, 100)]; + [coordinate tap]; + [self waitForExpectationsWithTimeout:2 handler:nil]; + + XCUIElement *annotation = mapElement.buttons[@"MGLMapViewAnnotation 0"]; + CGRect frame = annotation.frame; + + // Make sure the annotation stays in place when zooming in. + [mapElement doubleTap]; + CGRect frameAfterZoomingIn = annotation.frame; + XCTAssertEqualWithAccuracy(frame.origin.x, frameAfterZoomingIn.origin.x, 0.1, @"Annotation moved after zooming in."); + XCTAssertEqualWithAccuracy(frame.origin.y, frameAfterZoomingIn.origin.y, 0.1, @"Annotation moved after zooming in."); + XCTAssertEqualWithAccuracy(frame.size.width, frameAfterZoomingIn.size.width, 0.1, @"Annotation resized after zooming in."); + XCTAssertEqualWithAccuracy(frame.size.height, frameAfterZoomingIn.size.height, 0.1, @"Annotation resized after zooming in."); + + // Make sure the annotation moves after panning. + CGVector offset = CGVectorMake(50, 0); + XCUICoordinate *startCoordinate = [[mapElement coordinateWithNormalizedOffset:CGVectorMake(0, 0)] + coordinateWithOffset:CGVectorMake(100, 100)]; + XCUICoordinate *endCoordinate = [startCoordinate coordinateWithOffset:offset]; + [startCoordinate pressForDuration:0 thenDragToCoordinate:endCoordinate]; + [mapElement tap]; + CGRect frameAfterPanning = annotation.frame; + XCTAssertLessThanOrEqual(frame.origin.x + offset.dx, frameAfterPanning.origin.x, @"Annotation moved after panning right."); + XCTAssertEqualWithAccuracy(frame.origin.y + offset.dy, frameAfterPanning.origin.y, 0.1, @"Annotation moved after panning right."); + XCTAssertEqualWithAccuracy(frame.size.width, frameAfterPanning.size.width, 0.1, @"Annotation resized after panning right."); + XCTAssertEqualWithAccuracy(frame.size.height, frameAfterPanning.size.height, 0.1, @"Annotation resized after panning right."); +} + +- (void)testRemoveAnnotations { + XCUIApplication *app = [[XCUIApplication alloc] init]; + + // Drop a pin. + XCUIElement *mapElement = [[XCUIApplication alloc] init].otherElements[@"MGLMapView"]; + XCUIElement *mapProxyElement = mapElement.buttons[@"MGLMapViewProxyAccessibilityElement"]; + [self expectationForPredicate:[NSPredicate predicateWithFormat:@"exists == true"] evaluatedWithObject:mapProxyElement handler:nil]; + [mapElement pressForDuration:1.1]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + + // Remove all annotations, closing the callout view. + [app.navigationBars[@"MBXNavigationBar"].buttons[@"MBXSettingsButton"] tap]; + [app.sheets[@"Map Settings"] swipeUp]; + XCUIElementQuery *settingsCollectionViewsQuery = app.sheets[@"Map Settings"].collectionViews; + XCUIElement *removeAnnotationsButton = settingsCollectionViewsQuery.buttons[@"Remove Annotations"]; + [self expectationForPredicate:[NSPredicate predicateWithFormat:@"exists == false"] evaluatedWithObject:mapProxyElement handler:nil]; + [removeAnnotationsButton tap]; + [self waitForExpectationsWithTimeout:2 handler:nil]; + + XCTAssertFalse(mapElement.buttons[@"MGLMapViewAnnotation 0"].exists, @"Annotation should be gone after removing all annotations."); +} + +@end