summaryrefslogtreecommitdiff
path: root/platform/ios/test/MGLAnnotationViewTests.mm
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/test/MGLAnnotationViewTests.mm')
-rw-r--r--platform/ios/test/MGLAnnotationViewTests.mm336
1 files changed, 336 insertions, 0 deletions
diff --git a/platform/ios/test/MGLAnnotationViewTests.mm b/platform/ios/test/MGLAnnotationViewTests.mm
new file mode 100644
index 0000000000..b2ce145535
--- /dev/null
+++ b/platform/ios/test/MGLAnnotationViewTests.mm
@@ -0,0 +1,336 @@
+#import <Mapbox/Mapbox.h>
+#import <XCTest/XCTest.h>
+
+#import "../../darwin/src/MGLGeometry_Private.h"
+
+static NSString * const MGLTestAnnotationReuseIdentifer = @"MGLTestAnnotationReuseIdentifer";
+
+
+@interface MGLMapView (Tests)
+@property (nonatomic) MGLCameraChangeReason cameraChangeReasonBitmask;
+@end
+
+
+
+@interface MGLCustomAnnotationView : MGLAnnotationView
+
+@end
+
+@implementation MGLCustomAnnotationView
+
+- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier {
+ return [super initWithReuseIdentifier:@"reuse-id"];
+}
+
+@end
+
+@interface MGLAnnotationView (Test)
+
+@property (nonatomic) MGLMapView *mapView;
+@property (nonatomic, readwrite) MGLAnnotationViewDragState dragState;
+- (void)setDragState:(MGLAnnotationViewDragState)dragState;
+
+@end
+
+@interface MGLMapView (Test)
+@property (nonatomic) UIView<MGLCalloutView> *calloutViewForSelectedAnnotation;
+@end
+
+@interface MGLTestAnnotation : NSObject <MGLAnnotation>
+@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
+@end
+
+@implementation MGLTestAnnotation
+@end
+
+@interface MGLTestCalloutView: UIView<MGLCalloutView>
+@property (nonatomic) BOOL didCallDismissCalloutAnimated;
+@property (nonatomic, strong) id <MGLAnnotation> representedObject;
+@property (nonatomic, strong) UIView *leftAccessoryView;
+@property (nonatomic, strong) UIView *rightAccessoryView;
+@property (nonatomic, weak) id<MGLCalloutViewDelegate> delegate;
+@end
+
+@implementation MGLTestCalloutView
+
+- (void)dismissCalloutAnimated:(BOOL)animated
+{
+ _didCallDismissCalloutAnimated = YES;
+}
+
+- (void)presentCalloutFromRect:(CGRect)rect inView:(nonnull UIView *)view constrainedToRect:(CGRect)constrainedRect animated:(BOOL)animated {}
+
+@end
+
+@interface MGLAnnotationViewTests : XCTestCase <MGLMapViewDelegate>
+@property (nonatomic) XCTestExpectation *expectation;
+@property (nonatomic) MGLMapView *mapView;
+@property (nonatomic, weak) MGLAnnotationView *annotationView;
+@property (nonatomic) NSInteger annotationSelectedCount;
+@property (nonatomic) void (^prepareAnnotationView)(MGLAnnotationView*);
+@end
+
+@implementation MGLAnnotationViewTests
+
+- (void)setUp
+{
+ [super setUp];
+ _mapView = [[MGLMapView alloc] initWithFrame:CGRectMake(0, 0, 64, 64)];
+ _mapView.delegate = self;
+}
+
+- (void)testAnnotationView
+{
+ _expectation = [self expectationWithDescription:@"annotation property"];
+
+ MGLTestAnnotation *annotation = [[MGLTestAnnotation alloc] init];
+ [_mapView addAnnotation:annotation];
+
+ [self waitForExpectationsWithTimeout:1 handler:nil];
+
+ XCTAssert(_mapView.annotations.count == 1, @"number of annotations should be 1");
+ XCTAssertNotNil(_annotationView.annotation, @"annotation property should not be nil");
+ XCTAssertNotNil(_annotationView.mapView, @"mapView property should not be nil");
+
+ MGLTestCalloutView *testCalloutView = [[MGLTestCalloutView alloc] init];
+ _mapView.calloutViewForSelectedAnnotation = testCalloutView;
+ _annotationView.dragState = MGLAnnotationViewDragStateStarting;
+ XCTAssertTrue(testCalloutView.didCallDismissCalloutAnimated, @"callout view was not dismissed");
+
+ [_mapView removeAnnotation:_annotationView.annotation];
+
+ XCTAssert(_mapView.annotations.count == 0, @"number of annotations should be 0");
+ XCTAssertNil(_annotationView.annotation, @"annotation property should be nil");
+}
+
+- (void)testCustomAnnotationView
+{
+ MGLCustomAnnotationView *customAnnotationView = [[MGLCustomAnnotationView alloc] initWithReuseIdentifier:@"reuse-id"];
+ XCTAssertNotNil(customAnnotationView);
+}
+
+- (void)testSelectingOffscreenAnnotation
+{
+ // Partial test for https://github.com/mapbox/mapbox-gl-native/issues/9790
+
+ // This isn't quite the same as in updateAnnotationViews, but should be sufficient for this test.
+ MGLCoordinateBounds coordinateBounds = [_mapView convertRect:_mapView.bounds toCoordinateBoundsFromView:_mapView];
+
+ // -90 latitude is invalid. TBD.
+ BOOL anyOffscreen = NO;
+ NSInteger selectionCount = 0;
+
+ for (NSInteger latitude = -89; latitude <= 90; latitude += 10)
+ {
+ for (NSInteger longitude = -180; longitude <= 180; longitude += 10)
+ {
+ MGLTestAnnotation *annotation = [[MGLTestAnnotation alloc] init];
+
+ annotation.coordinate = CLLocationCoordinate2DMake(latitude, longitude);
+ [_mapView addAnnotation:annotation];
+
+ if (!(MGLCoordinateInCoordinateBounds(annotation.coordinate, coordinateBounds)))
+ anyOffscreen = YES;
+
+ XCTAssertNil(_mapView.selectedAnnotations.firstObject, @"There should be no selected annotation");
+
+ // First selection
+ [_mapView selectAnnotation:annotation animated:NO];
+ selectionCount++;
+
+ XCTAssert(_mapView.selectedAnnotations.count == 1, @"There should only be 1 selected annotation");
+ XCTAssertEqualObjects(_mapView.selectedAnnotations.firstObject, annotation, @"The annotation should be selected");
+
+ // Deselect
+ [_mapView deselectAnnotation:annotation animated:NO];
+ XCTAssert(_mapView.selectedAnnotations.count == 0, @"There should be no selected annotations");
+
+ // Second selection
+ _mapView.selectedAnnotations = @[annotation];
+ selectionCount++;
+
+ XCTAssert(_mapView.selectedAnnotations.count == 1, @"There should be 1 selected annotation");
+ XCTAssertEqualObjects(_mapView.selectedAnnotations.firstObject, annotation, @"The annotation should be selected");
+
+ // Deselect
+ [_mapView deselectAnnotation:annotation animated:NO];
+ XCTAssert(_mapView.selectedAnnotations.count == 0, @"There should be no selected annotations");
+ }
+ }
+
+ XCTAssert(anyOffscreen, @"At least one of these annotations should be offscreen");
+ XCTAssertEqual(selectionCount, self.annotationSelectedCount, @"-mapView:didSelectAnnotation: should be called for each selection");
+}
+
+- (void)testSelectingOnscreenAnnotationThatHasNotBeenAdded {
+ // See https://github.com/mapbox/mapbox-gl-native/issues/11476
+
+ // This bug occurs under the following conditions:
+ //
+ // - There are content insets (e.g. navigation bar) for the compare against
+ // CGRectZero (now CGRectNull)
+ // - annotationView.enabled == NO - Currently this can happen if you use
+ // `-initWithFrame:` rather than one of the provided initializers
+ //
+
+ self.prepareAnnotationView = ^(MGLAnnotationView *view) {
+ view.enabled = NO;
+ };
+
+ self.mapView.contentInset = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0);
+
+ MGLCameraChangeReason reasonBefore = self.mapView.cameraChangeReasonBitmask;
+ XCTAssert(reasonBefore == MGLCameraChangeReasonNone, @"Camera should not have moved at start of test");
+
+ // Create annotation
+ MGLPointFeature *point = [[MGLPointFeature alloc] init];
+ point.title = NSStringFromSelector(_cmd);
+ point.coordinate = CLLocationCoordinate2DMake(0.0, 0.0);
+
+ MGLCoordinateBounds coordinateBounds = [self.mapView convertRect:self.mapView.bounds toCoordinateBoundsFromView:self.mapView];
+ XCTAssert(MGLCoordinateInCoordinateBounds(point.coordinate, coordinateBounds), @"The test point should be within the visible map view");
+
+ // Select on screen annotation (DO NOT ADD FIRST).
+ [self.mapView selectAnnotation:point animated:YES];
+
+ // Expect - the camera NOT to move.
+ MGLCameraChangeReason reasonAfter = self.mapView.cameraChangeReasonBitmask;
+ XCTAssert(reasonAfter == MGLCameraChangeReasonNone, @"Camera should not have moved");
+}
+
+- (void)checkDefaultPropertiesForAnnotationView:(MGLAnnotationView*)view {
+ XCTAssertNil(view.annotation);
+ XCTAssertNil(view.reuseIdentifier);
+ XCTAssertEqual(view.centerOffset.dx, 0.0);
+ XCTAssertEqual(view.centerOffset.dy, 0.0);
+ XCTAssertFalse(view.scalesWithViewingDistance);
+ XCTAssertFalse(view.rotatesToMatchCamera);
+ XCTAssertFalse(view.isSelected);
+ XCTAssert(view.isEnabled);
+ XCTAssertFalse(view.isDraggable);
+ XCTAssertEqual(view.dragState, MGLAnnotationViewDragStateNone);
+}
+
+- (void)testAnnotationViewInitWithFrame {
+ CGRect frame = CGRectMake(10.0, 10.0, 100.0, 100.0);
+ MGLAnnotationView *view = [[MGLAnnotationView alloc] initWithFrame:frame];
+ [self checkDefaultPropertiesForAnnotationView:view];
+}
+
+- (void)testAnnotationViewInitWithReuseIdentifier {
+ MGLAnnotationView *view = [[MGLAnnotationView alloc] initWithReuseIdentifier:nil];
+ [self checkDefaultPropertiesForAnnotationView:view];
+}
+
+- (void)testSelectingADisabledAnnotationView {
+ self.prepareAnnotationView = ^(MGLAnnotationView *view) {
+ view.enabled = NO;
+ };
+
+ // Create annotation
+ MGLPointFeature *point = [[MGLPointFeature alloc] init];
+ point.title = NSStringFromSelector(_cmd);
+ point.coordinate = CLLocationCoordinate2DMake(0.0, 0.0);
+
+ XCTAssert(self.mapView.selectedAnnotations.count == 0, @"There should be 0 selected annotations");
+
+ [self.mapView selectAnnotation:point animated:NO];
+
+ XCTAssert(self.mapView.selectedAnnotations.count == 0, @"There should be 0 selected annotations");
+}
+
+- (void)disabled_testAddAnnotationWithBoundaryCoordinatesPENDING
+{
+ typedef struct {
+ CLLocationDegrees lat;
+ CLLocationDegrees lon;
+ BOOL expectation; // CLLocationCoordinate2DIsValid
+ BOOL mglExpectation; // MGLLocationCoordinate2DIsValid
+ } TestParam;
+
+ TestParam params[] = {
+ // Lat Lon CL MGL
+ { -91.0, 0.0, NO, NO },
+
+ { -90.0, 0.0, YES, YES }, // South pole. Should this really be considered an invalid coordinate?
+
+ // The rest for completeness
+ { -89.0, 0.0, YES, YES },
+ { 90.0, 0.0, YES, YES }, // North pole. Similarly, should this one be considered invalid?
+ { 91.0, 0.0, NO, NO },
+
+ { 0.0, -181.0, NO, YES },
+ { 0.0, -180.0, YES, YES },
+ { 0.0, 180.0, YES, YES },
+ { 0.0, 181.0, NO, YES },
+ };
+
+ for (int i = 0; i < sizeof(params)/sizeof(params[0]); i++) {
+ TestParam param = params[i];
+
+ // Essentially a deconstructed -[MGLMapView convertCoordinate:toPointToView]
+ CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(param.lat, param.lon);
+ NSString *coordDesc = [NSString stringWithFormat:@"(%0.1f,%0.1f)", param.lat, param.lon];
+
+ XCTAssert(CLLocationCoordinate2DIsValid(coordinate) == param.expectation, @"Unexpected CLLocationCoordinate2DIsValid for coordinate %@", coordDesc);
+ XCTAssert(MGLLocationCoordinate2DIsValid(coordinate) == param.mglExpectation, @"Unexpected MGLLocationCoordinate2DIsValid for coordinate %@", coordDesc);
+
+
+ CGPoint point = [_mapView convertCoordinate:coordinate toPointToView:_mapView];
+ (void)point;
+
+ // TODO:
+ XCTAssert(isnan(point.x) != param.expectation, @"Unexpected point.x for coordinate %@", coordDesc);
+ XCTAssert(isnan(point.y) != param.expectation, @"Unexpected point.y for coordinate %@", coordDesc);
+
+ // TODO: which one
+ if (param.expectation) {
+ // If we expect a valid coordinate, let's finally try to add an annotation
+
+ // The above method is called by the following, which will trigger CALayer to raise an
+ // exception
+ MGLTestAnnotation *annotation = [[MGLTestAnnotation alloc] init];
+ annotation.coordinate = coordinate;
+
+ @try {
+ [_mapView addAnnotation:annotation];
+ }
+ @catch (NSException *e) {
+ XCTFail("addAnnotation triggered exception: %@ for coordinate %@", e, coordDesc);
+ }
+ }
+ }
+}
+
+#pragma mark - MGLMapViewDelegate -
+
+
+- (MGLAnnotationView *)mapView:(MGLMapView *)mapView viewForAnnotation:(id<MGLAnnotation>)annotation
+{
+ MGLAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:MGLTestAnnotationReuseIdentifer];
+
+ if (!annotationView)
+ {
+ annotationView = [[MGLAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:MGLTestAnnotationReuseIdentifer];
+ }
+
+ if (self.prepareAnnotationView) {
+ self.prepareAnnotationView(annotationView);
+ }
+
+ _annotationView = annotationView;
+
+ return annotationView;
+}
+
+- (void)mapView:(MGLMapView *)mapView didAddAnnotationViews:(NSArray<MGLAnnotationView *> *)annotationViews
+{
+ [_expectation fulfill];
+}
+
+- (void)mapView:(MGLMapView *)mapView didSelectAnnotation:(id<MGLAnnotation>)annotation
+{
+ self.annotationSelectedCount++;
+}
+
+@end