summaryrefslogtreecommitdiff
path: root/platform/ios/Integration Tests
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/Integration Tests')
-rw-r--r--platform/ios/Integration Tests/MGLCameraTransitionTests.mm398
-rw-r--r--platform/ios/Integration Tests/MGLMapViewIntegrationTest.h13
-rw-r--r--platform/ios/Integration Tests/MGLMapViewIntegrationTest.m12
-rw-r--r--platform/ios/Integration Tests/MGLShapeSourceTests.m126
4 files changed, 542 insertions, 7 deletions
diff --git a/platform/ios/Integration Tests/MGLCameraTransitionTests.mm b/platform/ios/Integration Tests/MGLCameraTransitionTests.mm
new file mode 100644
index 0000000000..d5c288fbb9
--- /dev/null
+++ b/platform/ios/Integration Tests/MGLCameraTransitionTests.mm
@@ -0,0 +1,398 @@
+#import "MGLMapViewIntegrationTest.h"
+#import "../../darwin/src/MGLGeometry_Private.h"
+
+@interface MBCameraTransitionTests : MGLMapViewIntegrationTest
+@end
+
+@implementation MBCameraTransitionTests
+
+- (void)testSetAndResetNorthWithDispatchAsyncInDelegateMethod {
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
+ expectation.expectedFulfillmentCount = 2;
+ expectation.assertForOverFulfill = YES;
+
+ __weak typeof(self) weakself = self;
+
+ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) {
+
+ MBCameraTransitionTests *strongSelf = weakself;
+
+ if (!strongSelf) return;
+
+ [expectation fulfill];
+
+ MGLTestAssert(strongSelf, mapView.userTrackingMode != MGLUserTrackingModeFollowWithHeading);
+ if (mapView.direction != 0.0) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [mapView resetNorth];
+ });
+ }
+ };
+
+ [self.mapView setDirection:90 animated:YES];
+
+ // loop, render, and wait
+ [self waitForExpectations:@[expectation] timeout:1.5];
+}
+
+
+- (void)testSetAndResetNorthInDelegateMethod {
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
+ expectation.expectedFulfillmentCount = 2;
+ expectation.assertForOverFulfill = YES;
+
+ __weak typeof(self) weakself = self;
+
+ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) {
+
+ MBCameraTransitionTests *strongSelf = weakself;
+
+ if (!strongSelf) return;
+
+ [expectation fulfill];
+
+ MGLTestAssert(strongSelf, mapView.userTrackingMode != MGLUserTrackingModeFollowWithHeading);
+ if (mapView.direction != 0.0) {
+ NSLog(@"Reset to north");
+ [mapView resetNorth];
+ }
+ };
+
+ [self.mapView setDirection:90 animated:YES];
+ [self waitForExpectations:@[expectation] timeout:1.5];
+}
+
+- (void)testInterruptingAndResetNorthOnlyOnceInIsChanging {
+
+ // Reset to non-zero, prior to testing
+ [self.mapView setDirection:45 animated:NO];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
+ expectation.expectedFulfillmentCount = 1;
+ expectation.assertForOverFulfill = YES;
+
+ __weak typeof(self) weakself = self;
+ __block BOOL startedReset = NO;
+ __block BOOL finishedReset = NO;
+
+ self.regionIsChanging = ^(MGLMapView *mapView) {
+ MBCameraTransitionTests *strongSelf = weakself;
+ if (!strongSelf) return;
+
+ if (!startedReset) {
+ NSLog(@"Reset to north, interrupting the previous transition");
+ startedReset = YES;
+ [mapView resetNorth];
+ finishedReset = YES;
+ }
+ };
+
+ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) {
+ MBCameraTransitionTests *strongSelf = weakself;
+ if (!strongSelf) return;
+
+ MGLTestAssert(strongSelf, startedReset);
+
+ if (finishedReset) {
+ MGLTestAssert(strongSelf, !(reason & MGLCameraChangeReasonTransitionCancelled));
+ [expectation fulfill];
+ }
+ else {
+ MGLTestAssert(strongSelf, reason & MGLCameraChangeReasonTransitionCancelled);
+ }
+ };
+
+ [self.mapView setDirection:90 animated:YES];
+ [self waitForExpectations:@[expectation] timeout:1.5];
+
+ XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction);
+}
+
+- (void)testSetCenterCancelsTransitions {
+ XCTestExpectation *cameraIsInDCExpectation = [self expectationWithDescription:@"camera reset to DC"];
+
+ CLLocationCoordinate2D dc = CLLocationCoordinate2DMake(38.894368, -77.036487);
+ CLLocationCoordinate2D dc_west = CLLocationCoordinate2DMake(38.894368, -77.076487);
+
+ double zoomLevel = 15.0;
+
+ [self.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
+ [self.mapView setCenterCoordinate:dc_west zoomLevel:zoomLevel animated:YES];
+
+ __weak typeof(self) weakself = self;
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.15 * NSEC_PER_SEC),
+ dispatch_get_main_queue(),
+ ^{
+ MBCameraTransitionTests *strongSelf = weakself;
+
+ [strongSelf.mapView setCenterCoordinate:dc zoomLevel:zoomLevel animated:NO];
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ dc.latitude,
+ strongSelf.mapView.centerCoordinate.latitude,
+ 0.0005,
+ @"setting center coordinate should cancel transitions");
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ dc.longitude,
+ strongSelf.mapView.centerCoordinate.longitude,
+ 0.0005,
+ @"setting center coordinate should cancel transitions");
+ [cameraIsInDCExpectation fulfill];
+ });
+
+ [self waitForExpectations:@[cameraIsInDCExpectation] timeout:10.0];
+}
+
+- (void)testSetCenterCoordinateInDelegateMethod {
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
+ expectation.expectedFulfillmentCount = 2;
+ expectation.assertForOverFulfill = YES;
+
+ __weak typeof(self) weakself = self;
+ __block NSInteger delegateCallCount = 0;
+
+ CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
+ CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(-40.0, -40.0);
+
+ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) {
+
+ MBCameraTransitionTests *strongSelf = weakself;
+
+ if (!strongSelf) return;
+
+ MGLTestAssert(strongSelf, mapView.userTrackingMode != MGLUserTrackingModeFollowWithHeading);
+
+ CLLocationCoordinate2D center = mapView.centerCoordinate;
+
+ switch(delegateCallCount) {
+ case 0:
+ {
+ // Our center coordinate should match our target (assuming we're not
+ // constrained by zoom level)
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ target.longitude,
+ center.longitude,
+ 0.0005,
+ @"center coordinate longitude should be at target");
+
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ target.latitude,
+ center.latitude,
+ 0.0005,
+ @"center coordinate latitude should be at target");
+
+ // Now set another coordinate.
+ // Should take MGLAnimationDuration seconds (0.3s)
+ [mapView setCenterCoordinate:target2 animated:YES];
+ break;
+ }
+
+ case 1:
+ {
+ // Our center coordinate should match our target (assuming we're not
+ // constrained by zoom level)
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ target2.longitude,
+ center.longitude,
+ 0.0005,
+ @"center coordinate longitude should be at target2");
+
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ target2.latitude,
+ center.latitude,
+ 0.0005,
+ @"center coordinate latitude should be at target2");
+ break;
+
+ }
+
+ default:
+ MGLTestFail(strongSelf);
+ break;
+ }
+
+ delegateCallCount++;
+
+ [expectation fulfill];
+ };
+
+ // Should take MGLAnimationDuration seconds (0.3)
+ [self.mapView setCenterCoordinate:target zoomLevel:15.0 animated:YES];
+ [self waitForExpectations:@[expectation] timeout:1.5];
+}
+
+- (void)testFlyToCameraInDelegateMethod {
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
+
+ __weak typeof(self) weakself = self;
+ __block NSInteger delegateCallCount = 0;
+ expectation.expectedFulfillmentCount = 3;
+ expectation.assertForOverFulfill = YES;
+
+ CLLocationCoordinate2D target = CLLocationCoordinate2DMake(40.0, 40.0);
+ CLLocationCoordinate2D target2 = CLLocationCoordinate2DMake(30.0, 30.0);
+
+ __block BOOL runloop = YES;
+
+ NSTimeInterval stop0 = CACurrentMediaTime();
+ __block NSTimeInterval stop1 = 0.0;
+ __block NSTimeInterval stop2 = 0.0;
+
+ double zoomLevel = 5.0;
+ double altitude = MGLAltitudeForZoomLevel(zoomLevel, 0.0, target.latitude, self.mapView.frame.size);
+
+ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) {
+
+ MBCameraTransitionTests *strongSelf = weakself;
+
+ if (!strongSelf) return;
+
+ MGLTestAssert(strongSelf, mapView.userTrackingMode != MGLUserTrackingModeFollowWithHeading);
+
+ CLLocationCoordinate2D center = mapView.centerCoordinate;
+
+ switch(delegateCallCount) {
+ case 0:
+ {
+ stop1 = CACurrentMediaTime();
+
+ // Our center coordinate should match our target (assuming we're not
+ // constrained by zoom level)
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ target.longitude,
+ center.longitude,
+ 0.0005,
+ @"center coordinate longitude should be at target");
+
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ target.latitude,
+ center.latitude,
+ 0.0005,
+ @"center coordinate latitude should be at target");
+
+ // Now set another coordinate.
+ MGLMapCamera *camera = [MGLMapCamera cameraLookingAtCenterCoordinate:target2
+ fromDistance:altitude
+ pitch:0.0
+ heading:0.0];
+
+ // flyToCamera can take a while...
+ [mapView flyToCamera:camera completionHandler:^{
+ MGLTestAssert(strongSelf, !runloop, @"Completion block should be called after delegate method");
+ [expectation fulfill];
+ stop2 = CACurrentMediaTime();
+ }];
+ break;
+ }
+
+ case 1:
+ {
+ // Our center coordinate should match our target (assuming we're not
+ // constrained by zoom level)
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ target2.longitude,
+ center.longitude,
+ 0.0005,
+ @"center coordinate longitude should be at target2");
+
+ MGLTestAssertEqualWithAccuracy(strongSelf,
+ target2.latitude,
+ center.latitude,
+ 0.0005,
+ @"center coordinate latitude should be at target2");
+
+ runloop = NO;
+ break;
+ }
+
+ default:
+ MGLTestFail(strongSelf);
+ break;
+ }
+
+ delegateCallCount++;
+
+ [expectation fulfill];
+ };
+
+ // Should take MGLAnimationDuration
+ [self.mapView setCenterCoordinate:target zoomLevel:zoomLevel animated:YES];
+
+ // Run the loop, so the camera can fly to the new camera
+ while (runloop) {
+ [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
+ }
+ [self waitForExpectations:@[expectation] timeout:0.5];
+
+ NSLog(@"setCenterCoordinate: %0.4fs", stop1 - stop0);
+ NSLog(@"flyToCamera: %0.4fs", stop2 - stop1);
+
+ XCTAssert(delegateCallCount == 2, @"Expecting 2 regionDidChange callbacks, got %ld", delegateCallCount); // Once for the setDirection and once for the reset north
+}
+
+#pragma mark - Pending tests
+
+- (void)disabled_testContinuallyResettingNorthInIsChangingPENDING {
+
+ // 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.
+ //
+ // Possible solutions/expectations:
+ // - If you set camera parameters that match the *current* target parameters
+ // then the transition could be a no-op. We'd need to consider any completion
+ // block
+ // - Ideally we would detect this case and disallow it.
+
+ // Reset to non-zero, prior to testing
+ [self.mapView setDirection:45 animated:NO];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
+ expectation.expectedFulfillmentCount = 2;
+ expectation.assertForOverFulfill = YES;
+
+ self.regionIsChanging = ^(MGLMapView *mapView) {
+ [mapView resetNorth];
+ };
+
+ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) {
+ [expectation fulfill];
+ };
+
+ [self.mapView setDirection:90 animated:YES];
+ [self waitForExpectations:@[expectation] timeout:1.5];
+
+ XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction);
+}
+
+- (void)disabled_testContinuallySettingCoordinateInIsChangingPENDING {
+ // See above comment in `-disabled_testContinuallyResettingNorthInIsChangingPENDING`
+
+ // Reset to non-zero, prior to testing
+ [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(0.0, 0.0) animated:NO];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
+ expectation.expectedFulfillmentCount = 2;
+ expectation.assertForOverFulfill = YES;
+
+ __weak typeof(self) weakself = self;
+
+ self.regionIsChanging = ^(MGLMapView *mapView) {
+ [weakself.mapView setCenterCoordinate:CLLocationCoordinate2DMake(-40.0, -40.0) animated:YES];
+ };
+
+ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) {
+ [expectation fulfill];
+ };
+
+ [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(40.0, 40.0) animated:YES];
+ [self waitForExpectations:@[expectation] timeout:1.5];
+
+ XCTAssertEqualWithAccuracy(self.mapView.direction, 0.0, 0.001, @"Camera should have reset to north. %0.3f", self.mapView.direction);
+}
+
+@end
diff --git a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h
index ab5d2cc46f..6c04ed9f84 100644
--- a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h
+++ b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.h
@@ -1,18 +1,23 @@
#import <XCTest/XCTest.h>
#import <Mapbox/Mapbox.h>
-#define TestFailWithSelf(myself, ...) \
+#define MGLTestFail(myself, ...) \
_XCTPrimitiveFail(myself, __VA_ARGS__)
+#define MGLTestAssert(myself, expression, ...) \
+ _XCTPrimitiveAssertTrue(myself, expression, @#expression, __VA_ARGS__)
+
+#define MGLTestAssertEqualWithAccuracy(myself, expression1, expression2, accuracy, ...) \
+ _XCTPrimitiveAssertEqualWithAccuracy(myself, expression1, @#expression1, expression2, @#expression2, accuracy, @#accuracy, __VA_ARGS__)
+
@interface MGLMapViewIntegrationTest : XCTestCase <MGLMapViewDelegate>
@property (nonatomic) MGLMapView *mapView;
@property (nonatomic) MGLStyle *style;
@property (nonatomic) XCTestExpectation *styleLoadingExpectation;
@property (nonatomic) XCTestExpectation *renderFinishedExpectation;
-@property (nonatomic) void (^regionDidChange)(MGLMapView *mapView, BOOL animated);
+@property (nonatomic) void (^regionWillChange)(MGLMapView *mapView, BOOL animated);
@property (nonatomic) void (^regionIsChanging)(MGLMapView *mapView);
-
-
+@property (nonatomic) void (^regionDidChange)(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated);
// Utility methods
- (void)waitForMapViewToFinishLoadingStyleWithTimeout:(NSTimeInterval)timeout;
diff --git a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m
index fc3229c83b..c42b8eef89 100644
--- a/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m
+++ b/platform/ios/Integration Tests/MGLMapViewIntegrationTest.m
@@ -45,9 +45,9 @@
self.renderFinishedExpectation = nil;
}
-- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
- if (self.regionDidChange) {
- self.regionDidChange(mapView, animated);
+- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
+ if (self.regionWillChange) {
+ self.regionWillChange(mapView, animated);
}
}
@@ -57,6 +57,12 @@
}
}
+- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated {
+ if (self.regionDidChange) {
+ self.regionDidChange(mapView, reason, animated);
+ }
+}
+
#pragma mark - Utilities
- (void)waitForMapViewToFinishLoadingStyleWithTimeout:(NSTimeInterval)timeout {
diff --git a/platform/ios/Integration Tests/MGLShapeSourceTests.m b/platform/ios/Integration Tests/MGLShapeSourceTests.m
new file mode 100644
index 0000000000..088a9b011e
--- /dev/null
+++ b/platform/ios/Integration Tests/MGLShapeSourceTests.m
@@ -0,0 +1,126 @@
+//
+// MBShapeSourceTests.m
+// integration
+//
+// Created by Julian Rex on 4/5/18.
+// Copyright © 2018 Mapbox. All rights reserved.
+//
+
+#import "MGLMapViewIntegrationTest.h"
+
+@interface MGLShapeSourceTests : MGLMapViewIntegrationTest
+@end
+
+@implementation MGLShapeSourceTests
+
+- (void)testSettingShapeSourceToNilInRegionDidChange {
+
+ NSMutableArray *features = [[NSMutableArray alloc] init];
+
+ for (NSUInteger i = 0; i <= 180; i+=5) {
+ CLLocationCoordinate2D coord[4] = {
+ CLLocationCoordinate2DMake(round(0), round(i)),
+ CLLocationCoordinate2DMake(round(20), round(i)),
+ CLLocationCoordinate2DMake(round(0), round(i / 2 )),
+ CLLocationCoordinate2DMake(round(20), round(i / 2))};
+
+ MGLPolygonFeature *feature = [MGLPolygonFeature polygonWithCoordinates:coord count:4];
+ [features addObject:feature];
+ }
+
+ MGLShapeSource *shapeSource = [[MGLShapeSource alloc] initWithIdentifier:@"source" features:features options:nil];
+ [self.style addSource:shapeSource];
+
+ MGLFillStyleLayer *layer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"layer" source:shapeSource];
+ layer.fillOpacity = [NSExpression expressionForConstantValue:@0.5];
+ [self.style addLayer:layer];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
+ expectation.expectedFulfillmentCount = 1;
+ expectation.assertForOverFulfill = YES;
+
+ __weak typeof(self) weakself = self;
+ __block NSInteger delegateCallCount = 0;
+
+ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) {
+
+ MGLShapeSourceTests *strongSelf = weakself;
+
+ if (!strongSelf)
+ return;
+
+ delegateCallCount++;
+
+ // Setting the shapeSource.shape = nil, was causing an infinite loop, so here
+ // we check for a runaway call. 10 here is arbitrary. We could argue that this
+ // should check that the call count is only 1, however in this case we particularly
+ // want to check for the infinite loop.
+ // See https://github.com/mapbox/mapbox-gl-native/issues/11180
+
+ if (delegateCallCount > 10) {
+ MGLTestFail(strongSelf);
+ }
+ else {
+ shapeSource.shape = nil;
+ }
+
+ [expectation fulfill];
+ };
+
+ // setCenterCoordinate is NOT animated here.
+ [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0)];
+ [self waitForExpectations:@[expectation] timeout:5.0];
+}
+
+- (void)testSettingShapeSourceToNilInRegionIsChanging {
+
+ NSMutableArray *features = [[NSMutableArray alloc] init];
+
+ for (NSUInteger i = 0; i <= 180; i+=5) {
+ CLLocationCoordinate2D coord[4] = {
+ CLLocationCoordinate2DMake(round(0), round(i)),
+ CLLocationCoordinate2DMake(round(20), round(i)),
+ CLLocationCoordinate2DMake(round(0), round(i / 2 )),
+ CLLocationCoordinate2DMake(round(20), round(i / 2))};
+
+ MGLPolygonFeature *feature = [MGLPolygonFeature polygonWithCoordinates:coord count:4];
+ [features addObject:feature];
+ }
+
+ MGLShapeSource *shapeSource = [[MGLShapeSource alloc] initWithIdentifier:@"source" features:features options:nil];
+ [self.style addSource:shapeSource];
+
+ MGLFillStyleLayer *layer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"layer" source:shapeSource];
+ layer.fillOpacity = [NSExpression expressionForConstantValue:@0.5];
+ [self.style addLayer:layer];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"regionDidChange expectation"];
+ expectation.expectedFulfillmentCount = 1;
+ expectation.assertForOverFulfill = YES;
+
+ __block NSInteger delegateCallCount = 0;
+ __weak typeof(self) weakself = self;
+
+ self.regionIsChanging = ^(MGLMapView *mapView) {
+ // See https://github.com/mapbox/mapbox-gl-native/issues/11180
+ shapeSource.shape = nil;
+ };
+
+ self.regionDidChange = ^(MGLMapView *mapView, MGLCameraChangeReason reason, BOOL animated) {
+
+ delegateCallCount++;
+
+ if (delegateCallCount > 1) {
+ MGLTestFail(weakself);
+ }
+
+ [expectation fulfill];
+ };
+
+ // Should take MGLAnimationDuration seconds (0.3)
+ [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(10.0, 10.0) animated:YES];
+ [self waitForExpectations:@[expectation] timeout:1.0];
+}
+
+
+@end