diff options
Diffstat (limited to 'platform/ios/Integration Tests')
11 files changed, 985 insertions, 97 deletions
diff --git a/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m b/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.mm index f129277e87..1b3603419e 100644 --- a/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.m +++ b/platform/ios/Integration Tests/Annotation Tests/MGLAnnotationViewIntegrationTests.mm @@ -4,6 +4,13 @@ #import "MGLTestLocationManager.h" #import "MGLCompactCalloutView.h" +#import "MGLGeometry_Private.h" +#import "MGLMapView_Private.h" + +#include <mbgl/util/geo.hpp> +#include <mbgl/map/camera.hpp> +#include <mbgl/map/map.hpp> + @interface MGLTestCalloutView : MGLCompactCalloutView @property (nonatomic) BOOL implementsMarginHints; @end @@ -21,6 +28,10 @@ @interface MGLMapView (Tests) - (MGLAnnotationTag)annotationTagAtPoint:(CGPoint)point persistingResults:(BOOL)persist; +- (id <MGLAnnotation>)annotationWithTag:(MGLAnnotationTag)tag; +- (MGLMapCamera *)cameraByRotatingToDirection:(CLLocationDirection)degrees aroundAnchorPoint:(CGPoint)anchorPoint; +- (MGLMapCamera *)cameraByZoomingToZoomLevel:(double)zoom aroundAnchorPoint:(CGPoint)anchorPoint; +- (MGLMapCamera *)cameraForCameraOptions:(const mbgl::CameraOptions &)cameraOptions; @property (nonatomic) UIView<MGLCalloutView> *calloutViewForSelectedAnnotation; @end @@ -502,6 +513,267 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f }; XCTAssertEqual(originalFrame.origin.y + offset.y, offsetFrame.origin.y); } +#pragma mark - Rotating/zooming + +- (void)testSelectingAnnotationWhenMapIsRotated { + + CLLocationCoordinate2D coordinates[] = { + { 40.0, 40.0 }, + { NAN, NAN } + }; + + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + MGLPointAnnotation *annotation = annotations.firstObject; + + // Rotate + CLLocationDirection lastAngle = 0.0; + + srand48(0); + for (NSInteger iter = 0; iter < 10; iter++ ) { + + CLLocationDirection angle = (CLLocationDirection)((drand48()*1080.0) - 540.0); + + CGPoint anchor = CGPointMake(drand48()*CGRectGetWidth(self.mapView.bounds), drand48()*CGRectGetHeight(self.mapView.bounds)); + + NSString *activityTitle = [NSString stringWithFormat:@"Rotate to: %0.1f from: %0.1f", angle, lastAngle]; + [XCTContext runActivityNamed:activityTitle + block:^(id<XCTActivity> _Nonnull activity) { + + MGLMapCamera *toCamera = [self.mapView cameraByRotatingToDirection:angle aroundAnchorPoint:anchor]; + [self internalTestSelecting:annotation withCamera:toCamera]; + }]; + + lastAngle = angle; + } +} + +- (void)testSelectingAnnotationWhenMapIsScaled { + + CLLocationCoordinate2D coordinates[] = { + { 0.005, 0.005 }, + { NAN, NAN } + }; + + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + MGLPointAnnotation *annotation = annotations.firstObject; + + CGPoint anchor = CGPointMake(CGRectGetMidX(self.mapView.bounds), CGRectGetMidY(self.mapView.bounds)); + + srand48(0); + for (NSInteger iter = 0; iter < 10; iter++ ) { + + double zoom = (double)(drand48()*14.0); + + NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; + [XCTContext runActivityNamed:activityTitle + block:^(id<XCTActivity> _Nonnull activity) { + MGLMapCamera *toCamera = [self.mapView cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:anchor]; + [self internalTestSelecting:annotation withCamera:toCamera]; + }]; + } +} + +- (void)testSelectingAnnotationWhenMapIsScaledAndRotated { + + CLLocationCoordinate2D coordinates[] = { + { 0.005, 0.005 }, + { NAN, NAN } + }; + + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + MGLPointAnnotation *annotation = annotations.firstObject; + + srand48(0); + for (NSInteger iter = 0; iter < 10; iter++ ) { + + double zoom = (double)(7.0 + drand48()*7.0); + CLLocationDirection angle = (CLLocationDirection)((drand48()*1080.0) - 540.0); + + CGPoint anchor = CGPointMake(drand48()*CGRectGetWidth(self.mapView.bounds), drand48()*CGRectGetHeight(self.mapView.bounds)); + + NSString *activityTitle = [NSString stringWithFormat:@"Zoom to %0.1f", zoom]; + [XCTContext runActivityNamed:activityTitle + block:^(id<XCTActivity> _Nonnull activity) + { + mbgl::CameraOptions currentCameraOptions; + + currentCameraOptions.bearing = angle; + currentCameraOptions.anchor = mbgl::ScreenCoordinate { anchor.x, anchor.y }; + currentCameraOptions.zoom = zoom; + MGLMapCamera *toCamera = [self.mapView cameraForCameraOptions:currentCameraOptions]; + + [self internalTestSelecting:annotation withCamera:toCamera]; + }]; + } +} + + +- (void)testShowingAnnotationsThenSelectingAnimated { + [self internalTestShowingAnnotationsThenSelectingAnimated:YES]; +} + +- (void)testShowingAnnotationsThenSelecting { + [self internalTestShowingAnnotationsThenSelectingAnimated:NO]; +} + +- (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated { + srand48(0); + + CGFloat maxXPadding = std::max(CGRectGetWidth(self.mapView.bounds)/5.0, 100.0); + CGFloat maxYPadding = std::max(CGRectGetHeight(self.mapView.bounds)/5.0, 100.0); + + for (int i = 0; i < 10; i++) { + UIEdgeInsets edgePadding; + edgePadding.top = floor(drand48()*maxYPadding); + edgePadding.bottom = floor(drand48()*maxYPadding); + edgePadding.left = floor(drand48()*maxXPadding); + edgePadding.right = floor(drand48()*maxXPadding); + + UIEdgeInsets contentInsets; + contentInsets.top = floor(drand48()*maxYPadding); + contentInsets.bottom = floor(drand48()*maxYPadding); + contentInsets.left = floor(drand48()*maxXPadding); + contentInsets.right = floor(drand48()*maxXPadding); + + [self internalTestShowingAnnotationsThenSelectingAnimated:animated edgePadding:edgePadding contentInsets:contentInsets]; + } +} + +- (void)internalTestShowingAnnotationsThenSelectingAnimated:(BOOL)animated edgePadding:(UIEdgeInsets)edgeInsets contentInsets:(UIEdgeInsets)contentInsets { + CLLocationCoordinate2D coordinates[21]; + + for (int i = 0; i < (int)(sizeof(coordinates)/sizeof(coordinates[0])); i++) + { + coordinates[i].latitude = drand48(); + coordinates[i].longitude = drand48(); + } + coordinates[20] = CLLocationCoordinate2DMake(NAN, NAN); + + NSArray *annotations = [self internalAddAnnotationsAtCoordinates:coordinates]; + + XCTestExpectation *showCompleted = [self expectationWithDescription:@"showCompleted"]; + + self.mapView.contentInset = contentInsets; + [self.mapView showAnnotations:annotations + edgePadding:edgeInsets + animated:animated + completionHandler:^{ + [showCompleted fulfill]; + }]; + + [self waitForExpectations:@[showCompleted] timeout:3.5]; + + // These tests will fail if this isn't here. But this isn't quite what we're + // seeing in https://github.com/mapbox/mapbox-gl-native/issues/15106 + [self waitForCollisionDetectionToRun]; + + for (MGLPointAnnotation *point in annotations) { + [self internalSelectDeselectAnnotation:point]; + } + + [self.mapView removeAnnotations:annotations]; + self.mapView.contentInset = UIEdgeInsetsZero; + [self waitForCollisionDetectionToRun]; +} + +- (NSArray*)internalAddAnnotationsAtCoordinates:(CLLocationCoordinate2D*)coordinates +{ + __block NSMutableArray *annotations = [NSMutableArray array]; + + [XCTContext runActivityNamed:@"Map setup" + block:^(id<XCTActivity> _Nonnull activity) + { + + NSString * const MGLTestAnnotationReuseIdentifer = @"MGLTestAnnotationReuseIdentifer"; + + CGSize annotationSize = CGSizeMake(40.0, 40.0); + + self.viewForAnnotation = ^MGLAnnotationView*(MGLMapView *view, id<MGLAnnotation> annotation2) { + + if (![annotation2 isKindOfClass:[MGLPointAnnotation class]]) { + return nil; + } + + // No dequeue + MGLAnnotationView *annotationView = [[MGLAnnotationView alloc] initWithAnnotation:annotation2 reuseIdentifier:MGLTestAnnotationReuseIdentifer]; + annotationView.bounds = (CGRect){ .origin = CGPointZero, .size = annotationSize }; + annotationView.backgroundColor = UIColor.redColor; + annotationView.enabled = YES; + + return annotationView; + }; + + CLLocationCoordinate2D *coordinatePtr = coordinates; + while (!isnan(coordinatePtr->latitude)) { + CLLocationCoordinate2D coordinate = *coordinatePtr++; + + MGLPointAnnotation *annotation = [[MGLPointAnnotation alloc] init]; + annotation.title = NSStringFromSelector(_cmd); + annotation.coordinate = coordinate; + [annotations addObject:annotation]; + } + + [self.mapView addAnnotations:annotations]; + + }]; + + NSArray *copiedAnnotations = [annotations copy]; + annotations = nil; + + return copiedAnnotations; +} + +- (void)internalTestSelecting:(MGLPointAnnotation*)point withCamera:(MGLMapCamera*)camera { + + // Rotate + XCTestExpectation *rotationCompleted = [self expectationWithDescription:@"rotationCompleted"]; + [self.mapView setCamera:camera withDuration:0.1 animationTimingFunction:nil completionHandler:^{ + [rotationCompleted fulfill]; + }]; + + [self waitForExpectations:@[rotationCompleted] timeout:1.5]; + + // Collision detection may not have completed, if not we may not get our annotation. + [self waitForCollisionDetectionToRun]; + + // Look up annotation at point + [self internalSelectDeselectAnnotation:point]; +} + +- (void)internalSelectDeselectAnnotation:(MGLPointAnnotation*)point { + [XCTContext runActivityNamed:[NSString stringWithFormat:@"Select annotation: %@", point] + block:^(id<XCTActivity> _Nonnull activity) + { + CGPoint annotationPoint = [self.mapView convertCoordinate:point.coordinate toPointToView:self.mapView]; + + MGLAnnotationTag tagAtPoint = [self.mapView annotationTagAtPoint:annotationPoint persistingResults:YES]; + if (tagAtPoint != UINT32_MAX) + { + id <MGLAnnotation> annotation = [self.mapView annotationWithTag:tagAtPoint]; + XCTAssertNotNil(annotation); + + // Select + XCTestExpectation *selectionCompleted = [self expectationWithDescription:@"Selection completed"]; + [self.mapView selectAnnotation:annotation moveIntoView:NO animateSelection:NO completionHandler:^{ + [selectionCompleted fulfill]; + }]; + + [self waitForExpectations:@[selectionCompleted] timeout:0.05]; + + XCTAssert(self.mapView.selectedAnnotations.count == 1, @"There should only be 1 selected annotation"); + XCTAssertEqualObjects(self.mapView.selectedAnnotations.firstObject, annotation, @"The annotation should be selected"); + + // Deselect + [self.mapView deselectAnnotation:annotation animated:NO]; + } + else + { + XCTFail(@"Should be an annotation at this point: %@", NSStringFromCGPoint(annotationPoint)); + } + }]; + +} + #pragma mark - Utilities - (void)runRunLoop { @@ -529,6 +801,7 @@ static const CGPoint kAnnotationRelativeScale = { 0.05f, 0.125f }; - (void)waitForCollisionDetectionToRun { XCTAssertNil(self.renderFinishedExpectation, @"Incorrect test setup"); + [self.mapView setNeedsRerender]; self.renderFinishedExpectation = [self expectationWithDescription:@"Map view should be rendered"]; XCTestExpectation *timerExpired = [self expectationWithDescription:@"Timer expires"]; diff --git a/platform/ios/Integration Tests/Camera Tests/MGLCameraTransitionFinishTests.mm b/platform/ios/Integration Tests/Camera Tests/MGLCameraTransitionFinishTests.mm new file mode 100644 index 0000000000..1527e8dbe5 --- /dev/null +++ b/platform/ios/Integration Tests/Camera Tests/MGLCameraTransitionFinishTests.mm @@ -0,0 +1,109 @@ +#import "MGLMapViewIntegrationTest.h" +#import "MGLTestUtility.h" +#import "../../darwin/src/MGLGeometry_Private.h" + +#include <mbgl/map/camera.hpp> + +@interface MGLCameraTransitionFinishTests : MGLMapViewIntegrationTest +@end + +@implementation MGLCameraTransitionFinishTests + +- (void)testEaseToCompletionHandler { + + MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), + CLLocationCoordinate2DMake(1.0, 1.0)); + MGLMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; + + [self.mapView setCamera:camera + withDuration:0.0 + animationTimingFunction:nil + completionHandler:^{ + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:0.5]; +} + +- (void)testEaseToCompletionHandlerAnimated { + + MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), + CLLocationCoordinate2DMake(1.0, 1.0)); + MGLMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; + + [self.mapView setCamera:camera + withDuration:0.3 + animationTimingFunction:nil + completionHandler:^{ + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:0.5]; +} + +- (void)testFlyToCompletionHandler { + + MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), + CLLocationCoordinate2DMake(1.0, 1.0)); + MGLMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; + + [self.mapView flyToCamera:camera + withDuration:0.0 + completionHandler:^{ + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:0.5]; +} + +- (void)testFlyToCompletionHandlerAnimated { + + MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0.0, 0.0), + CLLocationCoordinate2DMake(1.0, 1.0)); + MGLMapCamera *camera = [self.mapView cameraThatFitsCoordinateBounds:bounds]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Completion block should be called"]; + + [self.mapView flyToCamera:camera + withDuration:0.3 + completionHandler:^{ + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:0.5]; +} +@end + +#pragma mark - camera transitions with NaN values + +@interface MGLMapView (MGLCameraTransitionFinishNaNTests) +- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera edgePadding:(UIEdgeInsets)insets; +@end + +@interface MGLCameraTransitionNaNZoomMapView: MGLMapView +@end + +@implementation MGLCameraTransitionNaNZoomMapView +- (mbgl::CameraOptions)cameraOptionsObjectForAnimatingToCamera:(MGLMapCamera *)camera edgePadding:(UIEdgeInsets)insets { + mbgl::CameraOptions options = [super cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets]; + options.zoom = NAN; + return options; +} +@end + +// Subclass the entire test suite, but with a different MGLMapView subclass +@interface MGLCameraTransitionFinishNaNTests : MGLCameraTransitionFinishTests +@end + +@implementation MGLCameraTransitionFinishNaNTests +- (MGLMapView *)mapViewForTestWithFrame:(CGRect)rect styleURL:(NSURL *)styleURL { + return [[MGLCameraTransitionNaNZoomMapView alloc] initWithFrame:rect styleURL:styleURL]; +} +@end + diff --git a/platform/ios/Integration Tests/MGLCameraTransitionTests.mm b/platform/ios/Integration Tests/Camera Tests/MGLCameraTransitionTests.mm index 9679c4c11f..27ab7964c1 100644 --- a/platform/ios/Integration Tests/MGLCameraTransitionTests.mm +++ b/platform/ios/Integration Tests/Camera Tests/MGLCameraTransitionTests.mm @@ -2,10 +2,10 @@ #import "MGLTestUtility.h" #import "../../darwin/src/MGLGeometry_Private.h" -@interface MBCameraTransitionTests : MGLMapViewIntegrationTest +@interface MGLCameraTransitionTests : MGLMapViewIntegrationTest @end -@implementation MBCameraTransitionTests +@implementation MGLCameraTransitionTests - (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod { @@ -17,7 +17,7 @@ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) { - MBCameraTransitionTests *strongSelf = weakself; + MGLCameraTransitionTests *strongSelf = weakself; if (!strongSelf) return; @@ -48,7 +48,7 @@ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) { - MBCameraTransitionTests *strongSelf = weakself; + MGLCameraTransitionTests *strongSelf = weakself; if (!strongSelf) return; @@ -79,7 +79,7 @@ __block BOOL finishedReset = NO; self.regionIsChanging = ^(MGLMapView *mapView) { - MBCameraTransitionTests *strongSelf = weakself; + MGLCameraTransitionTests *strongSelf = weakself; if (!strongSelf) return; if (!startedReset) { @@ -91,7 +91,7 @@ }; self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) { - MBCameraTransitionTests *strongSelf = weakself; + MGLCameraTransitionTests *strongSelf = weakself; if (!strongSelf) return; MGLTestAssert(strongSelf, startedReset); @@ -127,7 +127,7 @@ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - MBCameraTransitionTests *strongSelf = weakself; + MGLCameraTransitionTests *strongSelf = weakself; [strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO]; MGLTestAssertEqualWithAccuracy(strongSelf, @@ -160,7 +160,7 @@ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) { - MBCameraTransitionTests *strongSelf = weakself; + MGLCameraTransitionTests *strongSelf = weakself; if (!strongSelf) return; @@ -248,7 +248,7 @@ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) { - MBCameraTransitionTests *strongSelf = weakself; + MGLCameraTransitionTests *strongSelf = weakself; if (!strongSelf) return; @@ -333,7 +333,7 @@ #pragma mark - Pending tests -- (void)testContinuallyResettingNorthInIsChangingPENDING { +- (void)testContinuallyResettingNorthInIsChanging🙁{ // See https://github.com/mapbox/mapbox-gl-native/pull/11614 // This test currently fails, unsurprisingly, since we're continually // setting the camera to the same parameters during its update. @@ -365,8 +365,8 @@ XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction); } -- (void)testContinuallySettingCoordinateInIsChangingPENDING { - // See above comment in `-testContinuallyResettingNorthInIsChangingPENDING` +- (void)testContinuallySettingCoordinateInIsChanging🙁 { + // See above comment in `-testContinuallyResettingNorthInIsChanging🙁` // Reset to non-zero, prior to testing [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO]; diff --git a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h index dedafdb83a..08576e884a 100644 --- a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h +++ b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h @@ -26,6 +26,7 @@ @interface MGLMapViewIntegrationTest : XCTestCase <MGLMapViewDelegate> @property (nonatomic) MGLMapView *mapView; +@property (nonatomic) UIWindow *window; @property (nonatomic) MGLStyle *style; @property (nonatomic) XCTestExpectation *styleLoadingExpectation; @property (nonatomic) XCTestExpectation *renderFinishedExpectation; @@ -38,7 +39,7 @@ @property (nonatomic) id<MGLCalloutView> (^mapViewCalloutViewForAnnotation)(MGLMapView *mapView, id<MGLAnnotation> annotation); // Utility methods -- (NSString*)validAccessToken; - (void)waitForMapViewToFinishLoadingStyleWithTimeout:(NSTimeInterval)timeout; - (void)waitForMapViewToBeRenderedWithTimeout:(NSTimeInterval)timeout; +- (MGLMapView *)mapViewForTestWithFrame:(CGRect)rect styleURL:(NSURL *)styleURL; @end diff --git a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m index 2c89bb1c77..4095b4620b 100644 --- a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m +++ b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m @@ -2,50 +2,76 @@ @interface MGLMapView (MGLMapViewIntegrationTest) - (void)updateFromDisplayLink:(CADisplayLink *)displayLink; +- (void)setNeedsRerender; @end @implementation MGLMapViewIntegrationTest -- (void)invokeTest { - NSString *selector = NSStringFromSelector(self.invocation.selector); - BOOL isPendingTest = [selector hasSuffix:@"PENDING"]; ++ (XCTestSuite*)defaultTestSuite { - if (isPendingTest) { - NSString *runPendingTests = [[NSProcessInfo processInfo] environment][@"MAPBOX_RUN_PENDING_TESTS"]; - if (![runPendingTests boolValue]) { - printf("warning: '%s' is a pending test - skipping\n", selector.UTF8String); - return; + XCTestSuite *defaultTestSuite = [super defaultTestSuite]; + + NSArray *tests = defaultTestSuite.tests; + + XCTestSuite *newTestSuite = [XCTestSuite testSuiteWithName:defaultTestSuite.name]; + + BOOL runPendingTests = [[[NSProcessInfo processInfo] environment][@"MAPBOX_RUN_PENDING_TESTS"] boolValue]; + NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"]; + + for (XCTest *test in tests) { + + // Check for pending tests + if ([test.name containsString:@"PENDING"] || + [test.name containsString:@"🙁"]) { + if (!runPendingTests) { + printf("warning: '%s' is a pending test - skipping\n", test.name.UTF8String); + continue; + } + } + + // Check for tests that require a valid access token + if ([test.name containsString:@"🔒"]) { + if (!accessToken) { + printf("warning: MAPBOX_ACCESS_TOKEN env var is required for test '%s' - skipping.\n", test.name.UTF8String); + continue; + } } - } - - [super invokeTest]; -} -- (NSString*)validAccessToken { - NSString *accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"]; - if (!accessToken) { - printf("warning: MAPBOX_ACCESS_TOKEN env var is required for this test - skipping.\n"); - return nil; + [newTestSuite addTest:test]; } + + return newTestSuite; +} - [MGLAccountManager setAccessToken:accessToken]; - return accessToken; +- (MGLMapView *)mapViewForTestWithFrame:(CGRect)rect styleURL:(NSURL *)styleURL { + return [[MGLMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; } - (void)setUp { [super setUp]; - [MGLAccountManager setAccessToken:@"pk.feedcafedeadbeefbadebede"]; + NSString *accessToken; + + if ([self.name containsString:@"🔒"]) { + accessToken = [[NSProcessInfo processInfo] environment][@"MAPBOX_ACCESS_TOKEN"]; + + if (!accessToken) { + printf("warning: MAPBOX_ACCESS_TOKEN env var is required for test '%s' - trying anyway.\n", self.name.UTF8String); + } + } + + [MGLAccountManager setAccessToken:accessToken ?: @"pk.feedcafedeadbeefbadebede"]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - self.mapView = [[MGLMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; + self.mapView = [self mapViewForTestWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; self.mapView.delegate = self; UIView *superView = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds]; [superView addSubview:self.mapView]; - UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; - [window addSubview:superView]; - [window makeKeyAndVisible]; + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + [self.window addSubview:superView]; + [self.window makeKeyAndVisible]; if (!self.mapView.style) { [self waitForMapViewToFinishLoadingStyleWithTimeout:10]; @@ -57,6 +83,7 @@ self.renderFinishedExpectation = nil; self.mapView = nil; self.style = nil; + self.window = nil; [MGLAccountManager setAccessToken:nil]; [super tearDown]; @@ -129,13 +156,15 @@ XCTAssertNil(self.styleLoadingExpectation); self.styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; [self waitForExpectations:@[self.styleLoadingExpectation] timeout:timeout]; + self.styleLoadingExpectation = nil; } - (void)waitForMapViewToBeRenderedWithTimeout:(NSTimeInterval)timeout { XCTAssertNil(self.renderFinishedExpectation); - [self.mapView setNeedsDisplay]; + [self.mapView setNeedsRerender]; self.renderFinishedExpectation = [self expectationWithDescription:@"Map view should be rendered"]; [self waitForExpectations:@[self.renderFinishedExpectation] timeout:timeout]; + self.renderFinishedExpectation = nil; } - (void)waitForExpectations:(NSArray<XCTestExpectation *> *)expectations timeout:(NSTimeInterval)seconds { diff --git a/platform/ios/Integration Tests/MGLMapViewPendingBlockTests.m b/platform/ios/Integration Tests/MGLMapViewPendingBlockTests.m new file mode 100644 index 0000000000..c7925d7896 --- /dev/null +++ b/platform/ios/Integration Tests/MGLMapViewPendingBlockTests.m @@ -0,0 +1,366 @@ +#import "MGLMapViewIntegrationTest.h" +#import "MGLTestUtility.h" + +@interface MGLMapView (MGLMapViewPendingBlockTests) +@property (nonatomic) NSMutableArray *pendingCompletionBlocks; +- (void)pauseRendering:(__unused NSNotification *)notification; +@end + +@interface MGLMapViewPendingBlockTests : MGLMapViewIntegrationTest +@property (nonatomic, copy) void (^observation)(NSDictionary*); +@property (nonatomic) BOOL completionHandlerCalled; +@end + +@implementation MGLMapViewPendingBlockTests + +- (void)testSetCenterCoordinate { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetCenterCoordinateAnimated { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:YES + completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetVisibleCoordinateBounds { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + [strongSelf.mapView setVisibleCoordinateBounds:unitBounds + edgePadding:UIEdgeInsetsZero + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetVisibleCoordinateBoundsAnimated { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + [strongSelf.mapView setVisibleCoordinateBounds:unitBounds + edgePadding:UIEdgeInsetsZero + animated:YES + completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetCamera { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + MGLMapCamera *camera = [strongSelf.mapView cameraThatFitsCoordinateBounds:unitBounds]; + + [strongSelf.mapView setCamera:camera withDuration:0.0 animationTimingFunction:nil completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testSetCameraAnimated { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + MGLMapCamera *camera = [strongSelf.mapView cameraThatFitsCoordinateBounds:unitBounds]; + + [strongSelf.mapView setCamera:camera withDuration:0.3 animationTimingFunction:nil completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testFlyToCamera { + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + MGLMapCamera *camera = [strongSelf.mapView cameraThatFitsCoordinateBounds:unitBounds]; + + [strongSelf.mapView flyToCamera:camera withDuration:0.0 completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + +- (void)testFlyToCameraAnimated { + + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + MGLCoordinateBounds unitBounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(0, 0), CLLocationCoordinate2DMake(1, 1)); + MGLMapCamera *camera = [strongSelf.mapView cameraThatFitsCoordinateBounds:unitBounds]; + + [strongSelf.mapView flyToCamera:camera withDuration:0.3 completionHandler:completion]; + } + else { + completion(); + } + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:nil]; +} + + +#pragma mark - test interrupting regular rendering + +- (void)testSetCenterCoordinateSetHidden { + + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + dispatch_block_t addedToPending = ^{ + __typeof__(self) strongSelf = weakSelf; + + MGLTestAssert(strongSelf, !strongSelf.completionHandlerCalled); + + // Now hide the mapview + strongSelf.mapView.hidden = YES; + + MGLTestAssert(strongSelf, strongSelf.completionHandlerCalled); + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:addedToPending]; +} + +- (void)testSetCenterCoordinatePauseRendering { + + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + dispatch_block_t addedToPending = ^{ + __typeof__(self) strongSelf = weakSelf; + + MGLTestAssert(strongSelf, !strongSelf.completionHandlerCalled); + + // Pause rendering, stopping display link + [strongSelf.mapView pauseRendering:nil]; + + MGLTestAssert(strongSelf, strongSelf.completionHandlerCalled); + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:addedToPending]; +} + +- (void)testSetCenterCoordinateRemoveFromSuperview { + + __typeof__(self) weakSelf = self; + + void (^transition)(dispatch_block_t) = ^(dispatch_block_t completion) { + __typeof__(self) strongSelf = weakSelf; + + if (strongSelf) { + [strongSelf.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) + zoomLevel:10.0 + direction:0 + animated:NO + completionHandler:completion]; + } + else { + completion(); + } + }; + + dispatch_block_t addedToPending = ^{ + __typeof__(self) strongSelf = weakSelf; + + MGLTestAssert(strongSelf, !strongSelf.completionHandlerCalled); + + // Remove from window, triggering validateDisplayLink + [strongSelf.mapView removeFromSuperview]; + + MGLTestAssert(strongSelf, strongSelf.completionHandlerCalled); + }; + + [self internalTestCompletionBlockAddedToPendingForTestName:NSStringFromSelector(_cmd) + transition:transition + addToPendingCallback:addedToPending]; +} + +#pragma mark - Shared utility methods + +- (void)internalTestCompletionBlockAddedToPendingForTestName:(NSString *)testName + transition:(void (^)(dispatch_block_t))transition + addToPendingCallback:(dispatch_block_t)addToPendingCallback { + + XCTestExpectation *expectation = [self expectationWithDescription:testName]; + + __weak __typeof__(self) myself = self; + + dispatch_block_t block = ^{ + myself.completionHandlerCalled = YES; + [expectation fulfill]; + }; + + XCTAssertNotNil(transition); + transition(block); + + XCTAssert(!self.completionHandlerCalled); + XCTAssert(self.mapView.pendingCompletionBlocks.count == 0); + + __block BOOL blockAddedToPendingBlocks = NO; + + // Observes changes to pendingCompletionBlocks (including additions) + self.observation = ^(NSDictionary *change){ + + NSLog(@"change = %@ count = %lu", change, myself.mapView.pendingCompletionBlocks.count); + + NSArray *value = change[NSKeyValueChangeNewKey]; + + MGLTestAssert(myself, [value isKindOfClass:[NSArray class]]); + + if (value.count > 0) { + MGLTestAssert(myself, [value containsObject:block]); + MGLTestAssert(myself, !blockAddedToPendingBlocks); + if ([myself.mapView.pendingCompletionBlocks containsObject:block]) { + blockAddedToPendingBlocks = YES; + + if (addToPendingCallback) { + addToPendingCallback(); + } + } + } + }; + + [self.mapView addObserver:self forKeyPath:@"pendingCompletionBlocks" options:NSKeyValueObservingOptionNew context:_cmd]; + + [self waitForExpectations:@[expectation] timeout:0.5]; + + XCTAssert(blockAddedToPendingBlocks); + XCTAssert(self.completionHandlerCalled); + XCTAssert(self.mapView.pendingCompletionBlocks.count == 0); + + [self.mapView removeObserver:self forKeyPath:@"pendingCompletionBlocks" context:_cmd]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { + if (self.observation) { + self.observation(change); + } +} +@end diff --git a/platform/ios/Integration Tests/MGLSourceTests.swift b/platform/ios/Integration Tests/MGLSourceTests.swift new file mode 100644 index 0000000000..69fa0182b5 --- /dev/null +++ b/platform/ios/Integration Tests/MGLSourceTests.swift @@ -0,0 +1,45 @@ +import XCTest + +class MGLSourceTests: MGLMapViewIntegrationTest { + + // See testForRaisingExceptionsOnStaleStyleObjects for Obj-C sibling. + func testForRaisingExceptionsOnStaleStyleObjectsOnRemoveFromMapView() { + + guard + let configURL = URL(string: "mapbox://examples.2uf7qges") else { + XCTFail() + return + } + + let source = MGLVectorTileSource(identifier: "trees", configurationURL: configURL) + mapView.style?.addSource(source) + + let bundle = Bundle(for: type(of: self)) + + guard let styleURL = bundle.url(forResource: "one-liner", withExtension: "json") else { + XCTFail() + return + } + + styleLoadingExpectation = nil; + + mapView.centerCoordinate = CLLocationCoordinate2D(latitude : 38.897, longitude : -77.039) + mapView.zoomLevel = 10.5 + mapView.styleURL = styleURL + + waitForMapViewToFinishLoadingStyle(withTimeout: 10.0) + + let expect = expectation(description: "Remove source should error") + + do { + try mapView.style?.removeSource(source, error: ()) + } + catch let error as NSError { + XCTAssertEqual(error.domain, MGLErrorDomain) + XCTAssertEqual(error.code, MGLErrorCode.sourceCannotBeRemovedFromStyle.rawValue) + expect.fulfill() + } + + wait(for: [expect], timeout: 0.1) + } +} diff --git a/platform/ios/Integration Tests/MGLStyleLayerIntegrationTests.m b/platform/ios/Integration Tests/MGLStyleLayerIntegrationTests.m index 4501294f72..c018c457b9 100644 --- a/platform/ios/Integration Tests/MGLStyleLayerIntegrationTests.m +++ b/platform/ios/Integration Tests/MGLStyleLayerIntegrationTests.m @@ -58,4 +58,55 @@ [self waitForMapViewToBeRenderedWithTimeout:10]; } +- (void)testForRaisingExceptionsOnStaleStyleObjects { + self.mapView.centerCoordinate = CLLocationCoordinate2DMake(38.897,-77.039); + self.mapView.zoomLevel = 10.5; + + MGLVectorTileSource *source = [[MGLVectorTileSource alloc] initWithIdentifier:@"trees" configurationURL:[NSURL URLWithString:@"mapbox://examples.2uf7qges"]]; + [self.mapView.style addSource:source]; + + self.styleLoadingExpectation = nil; + [self.mapView setStyleURL:[[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]]; + [self waitForMapViewToFinishLoadingStyleWithTimeout:10]; + + XCTAssertNotNil(source.description); + XCTAssertThrowsSpecificNamed(source.configurationURL, NSException, MGLInvalidStyleSourceException, @"MGLSource should raise an exception if its core peer got invalidated"); +} + +- (void)testForRaisingExceptionsOnStaleLayerObject { + self.mapView.centerCoordinate = CLLocationCoordinate2DMake(38.897,-77.039); + self.mapView.zoomLevel = 10.5; + + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + + // Testing generated layers + MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"lineLayerID" source:source]; + MGLRasterStyleLayer *rasterLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"rasterLayerID" source:source]; + + [self.mapView.style addSource:source]; + [self.mapView.style addLayer:lineLayer]; + [self.mapView.style addLayer:rasterLayer]; + + XCTAssertNoThrow(lineLayer.isVisible); + XCTAssertNoThrow(rasterLayer.isVisible); + + XCTAssert(![source.description containsString:@"<unknown>"]); + XCTAssert(![lineLayer.description containsString:@"<unknown>"]); + XCTAssert(![rasterLayer.description containsString:@"<unknown>"]); + + self.styleLoadingExpectation = nil; + [self.mapView setStyleURL:[[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]]; + [self waitForMapViewToFinishLoadingStyleWithTimeout:10]; + + XCTAssert([source.description containsString:@"<unknown>"]); + XCTAssert([lineLayer.description containsString:@"<unknown>"]); + XCTAssert([rasterLayer.description containsString:@"<unknown>"]); + + XCTAssertThrowsSpecificNamed(lineLayer.isVisible, NSException, MGLInvalidStyleLayerException, @"Layer should raise an exception if its core peer got invalidated"); + XCTAssertThrowsSpecificNamed(rasterLayer.isVisible, NSException, MGLInvalidStyleLayerException, @"Layer should raise an exception if its core peer got invalidated"); + + XCTAssertThrowsSpecificNamed([self.mapView.style removeLayer:lineLayer], NSException, NSInvalidArgumentException, @"Style should raise an exception when attempting to remove an invalid layer (e.g. if its core peer got invalidated)"); + XCTAssertThrowsSpecificNamed([self.mapView.style removeLayer:rasterLayer], NSException, NSInvalidArgumentException, @"Style should raise an exception when attempting to remove an invalid layer (e.g. if its core peer got invalidated)"); +} @end diff --git a/platform/ios/Integration Tests/MGLStyleURLIntegrationTest.m b/platform/ios/Integration Tests/MGLStyleURLIntegrationTest.m index f9217bae5f..22de4c6aa5 100644 --- a/platform/ios/Integration Tests/MGLStyleURLIntegrationTest.m +++ b/platform/ios/Integration Tests/MGLStyleURLIntegrationTest.m @@ -6,36 +6,32 @@ @implementation MGLStyleURLIntegrationTest - (void)internalTestWithStyleSelector:(SEL)selector { - if (![self validAccessToken]) { - return; - } - self.mapView.styleURL = [MGLStyle performSelector:selector]; [self waitForMapViewToFinishLoadingStyleWithTimeout:5]; } -- (void)testLoadingStreetsStyleURL { +- (void)testLoadingStreetsStyleURL🔒 { [self internalTestWithStyleSelector:@selector(streetsStyleURL)]; } -- (void)testLoadingOutdoorsStyleURL { +- (void)testLoadingOutdoorsStyleURL🔒 { [self internalTestWithStyleSelector:@selector(outdoorsStyleURL)]; } -- (void)testLoadingLightStyleURL { +- (void)testLoadingLightStyleURL🔒 { [self internalTestWithStyleSelector:@selector(lightStyleURL)]; } -- (void)testLoadingDarkStyleURL { +- (void)testLoadingDarkStyleURL🔒 { [self internalTestWithStyleSelector:@selector(darkStyleURL)]; } -- (void)testLoadingSatelliteStyleURL { +- (void)testLoadingSatelliteStyleURL🔒 { [self internalTestWithStyleSelector:@selector(satelliteStyleURL)]; } -- (void)testLoadingSatelliteStreetsStyleURL { +- (void)testLoadingSatelliteStreetsStyleURL🔒 { [self internalTestWithStyleSelector:@selector(satelliteStreetsStyleURL)]; } diff --git a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterSwiftTests.swift b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterSwiftTests.swift index 0f2034b6b8..c3400b1fa2 100644 --- a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterSwiftTests.swift +++ b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterSwiftTests.swift @@ -15,12 +15,9 @@ class MGLMapSnapshotterSwiftTests: MGLMapViewIntegrationTest { return options } - func testCapturingSnapshotterInSnapshotCompletion() { + func testCapturingSnapshotterInSnapshotCompletion🔒() { // See the Obj-C testDeallocatingSnapshotterDuringSnapshot // This Swift test, is essentially the same except for capturing the snapshotter - guard validAccessToken() != nil else { - return - } let timeout: TimeInterval = 10.0 let expectation = self.expectation(description: "snapshot") diff --git a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m index 32e5fc782d..7707896203 100644 --- a/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m +++ b/platform/ios/Integration Tests/Snapshotter Tests/MGLMapSnapshotterTest.m @@ -26,11 +26,7 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates @implementation MGLMapSnapshotterTest -- (void)testMultipleSnapshotsWithASingleSnapshotter { - if (![self validAccessToken]) { - return; - } - +- (void)testMultipleSnapshotsWithASingleSnapshotter🔒 { CGSize size = self.mapView.bounds.size; XCTestExpectation *expectation = [self expectationWithDescription:@"snapshots"]; @@ -60,11 +56,8 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates [self waitForExpectations:@[expectation] timeout:10.0]; } -- (void)testDeallocatingSnapshotterDuringSnapshot { +- (void)testDeallocatingSnapshotterDuringSnapshot🔒 { // See also https://github.com/mapbox/mapbox-gl-native/issues/12336 - if (![self validAccessToken]) { - return; - } NSTimeInterval timeout = 10.0; XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot"]; @@ -108,16 +101,12 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates [self waitForExpectations:@[expectation] timeout:timeout]; } -- (void)testSnapshotterUsingNestedDispatchQueues { +- (void)testSnapshotterUsingNestedDispatchQueues🔒 { // This is the opposite pair to the above test `testDeallocatingSnapshotterDuringSnapshot` // The only significant difference is that the snapshotter is a `__block` variable, so // its lifetime should continue until it's set to nil in the completion block. // See also https://github.com/mapbox/mapbox-gl-native/issues/12336 - if (![self validAccessToken]) { - return; - } - NSTimeInterval timeout = 10.0; XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot"]; CGSize size = self.mapView.bounds.size; @@ -156,11 +145,7 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates [self waitForExpectations:@[expectation] timeout:timeout]; } -- (void)testCancellingSnapshot { - if (![self validAccessToken]) { - return; - } - +- (void)testCancellingSnapshot🔒 { XCTestExpectation *expectation = [self expectationWithDescription:@"snapshots"]; expectation.assertForOverFulfill = YES; expectation.expectedFulfillmentCount = 1; @@ -189,11 +174,7 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates [self waitForExpectations:@[expectation] timeout:5.0]; } -- (void)testAllocatingSnapshotOnBackgroundQueue { - if (![self validAccessToken]) { - return; - } - +- (void)testAllocatingSnapshotOnBackgroundQueue🔒 { XCTestExpectation *expectation = [self expectationWithDescription:@"snapshots"]; CGSize size = self.mapView.bounds.size; @@ -226,11 +207,7 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates [self waitForExpectations:@[expectation] timeout:2.0]; } -- (void)testSnapshotterFromBackgroundQueueShouldFail { - if (![self validAccessToken]) { - return; - } - +- (void)testSnapshotterFromBackgroundQueueShouldFail🔒 { CGSize size = self.mapView.bounds.size; CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(30.0, 30.0); @@ -281,12 +258,7 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates [self waitForExpectations:@[expectation] timeout:60.0]; } -- (void)testMultipleSnapshottersPENDING { - - if (![self validAccessToken]) { - return; - } - +- (void)testMultipleSnapshotters🔒🙁 { NSUInteger numSnapshots = 8; CGSize size = self.mapView.bounds.size; @@ -340,11 +312,7 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates [self waitForExpectations:@[expectation] timeout:60.0]; } -- (void)testSnapshotPointConversion { - if (![self validAccessToken]) { - return; - } - +- (void)testSnapshotPointConversion🔒 { CGSize size = self.mapView.bounds.size; XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot"]; @@ -382,11 +350,7 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates [self waitForExpectations:@[expectation] timeout:10.0]; } -- (void)testSnapshotPointConversionCoordinateOrdering { - if (![self validAccessToken]) { - return; - } - +- (void)testSnapshotPointConversionCoordinateOrdering🔒 { CGSize size = self.mapView.bounds.size; XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot"]; @@ -429,5 +393,62 @@ MGLMapSnapshotter* snapshotterWithCoordinates(CLLocationCoordinate2D coordinates [self waitForExpectations:@[expectation] timeout:10.0]; } +- (void)testSnapshotWithOverlayHandlerFailure { + if (![self validAccessToken]) { + return; + } + + CGSize size = self.mapView.bounds.size; + + XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot with overlay fails"]; + expectation.expectedFulfillmentCount = 2; + + CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(30.0, 30.0); + + MGLMapSnapshotter *snapshotter = snapshotterWithCoordinates(coord, size); + XCTAssertNotNil(snapshotter); + + [snapshotter startWithOverlayHandler:^(MGLMapSnapshotOverlay * _Nullable snapshotOverlay) { + UIGraphicsEndImageContext(); + [expectation fulfill]; + } completionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { + XCTAssertNil(snapshot); + XCTAssertNotNil(error); + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:10.0]; +} + +- (void)testSnapshotWithOverlayHandlerSuccess { + if (![self validAccessToken]) { + return; + } + + CGSize size = self.mapView.bounds.size; + CGRect snapshotRect = CGRectMake(0, 0, size.width, size.height); + + XCTestExpectation *expectation = [self expectationWithDescription:@"snapshot with overlay succeeds"]; + expectation.expectedFulfillmentCount = 2; + + CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(30.0, 30.0); + + MGLMapSnapshotter *snapshotter = snapshotterWithCoordinates(coord, size); + XCTAssertNotNil(snapshotter); + + [snapshotter startWithOverlayHandler:^(MGLMapSnapshotOverlay * _Nullable snapshotOverlay) { + CGContextSetFillColorWithColor(snapshotOverlay.context, [UIColor.greenColor CGColor]); + CGContextSetAlpha(snapshotOverlay.context, 0.2); + CGContextAddRect(snapshotOverlay.context, snapshotRect); + CGContextFillRect(snapshotOverlay.context, snapshotRect); + [expectation fulfill]; + } completionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { + XCTAssertNil(error); + XCTAssertNotNil(snapshot); + [expectation fulfill]; + }]; + + [self waitForExpectations:@[expectation] timeout:10.0]; +} @end |