From 3a553307653dc3b36eab10b51c7da95dee6256cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Mon, 23 Mar 2020 19:08:27 -0700 Subject: [PATCH] [ios, macos] Snapshot-test runtime styling in snapshots --- platform/darwin/test/MGLMapSnapshotterTests.m | 124 ++++++++++++++++-- .../Media.xcassets/Fixtures/Contents.json | 9 ++ .../MGLMapSnapshotterTests/Contents.json | 9 ++ .../background.imageset/Contents.json | 21 +++ .../background.imageset/background.png | Bin 0 -> 20008 bytes 5 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 platform/darwin/test/Media.xcassets/Fixtures/Contents.json create mode 100644 platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/Contents.json create mode 100644 platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/background.imageset/Contents.json create mode 100644 platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/background.imageset/background.png diff --git a/platform/darwin/test/MGLMapSnapshotterTests.m b/platform/darwin/test/MGLMapSnapshotterTests.m index a692b95c4..6e07cbad3 100644 --- a/platform/darwin/test/MGLMapSnapshotterTests.m +++ b/platform/darwin/test/MGLMapSnapshotterTests.m @@ -1,9 +1,58 @@ #import #import +MGLImage *MGLNormalizedImage(MGLImage *sourceImage) { +#if TARGET_OS_IPHONE + CGSize scaledSize = CGSizeMake(sourceImage.size.width * sourceImage.scale, sourceImage.size.height * sourceImage.scale); + UIGraphicsBeginImageContextWithOptions(scaledSize, NO, UIScreen.mainScreen.scale); + [sourceImage drawInRect:(CGRect){ .origin = CGPointZero, .size = scaledSize }]; + UIImage *normalizedImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return normalizedImage; +#else + return [NSImage imageWithSize:sourceImage.size flipped:NO drawingHandler:^BOOL(NSRect dstRect) { + [sourceImage drawInRect:dstRect]; + return YES; + }]; +#endif +} + +BOOL MGLEqualImages(MGLImage *leftImage, MGLImage *rightImage) { +#if TARGET_OS_IPHONE + NSData *leftData = UIImagePNGRepresentation(MGLNormalizedImage(leftImage)); + NSData *rightData = UIImagePNGRepresentation(MGLNormalizedImage(rightImage)); + return [leftData isEqualToData:rightData]; +#else + CGImageRef leftCGImage = [MGLNormalizedImage(leftImage) CGImageForProposedRect:nil context:nil hints:nil]; + NSBitmapImageRep *leftImageRep = [[NSBitmapImageRep alloc] initWithCGImage:leftCGImage]; + NSData *leftData = [leftImageRep representationUsingType:NSPNGFileType properties:@{}]; + + CGImageRef rightCGImage = [MGLNormalizedImage(rightImage) CGImageForProposedRect:nil context:nil hints:nil]; + NSBitmapImageRep *rightImageRep = [[NSBitmapImageRep alloc] initWithCGImage:rightCGImage]; + NSData *rightData = [rightImageRep representationUsingType:NSPNGFileType properties:@{}]; + + return [leftData isEqualToData:rightData]; +#endif +} + +MGLImage *MGLImageFromCurrentContext() { +#if TARGET_OS_IPHONE + return UIGraphicsGetImageFromCurrentImageContext(); +#else + CGContextRef context = NSGraphicsContext.currentContext.CGContext; + CGImageRef cgImage = CGBitmapContextCreateImage(context); + CGFloat scale = NSScreen.mainScreen.backingScaleFactor; + NSSize imageSize = NSMakeSize(CGImageGetWidth(cgImage) / scale, CGImageGetHeight(cgImage) / scale); + NSImage *image = [[NSImage alloc] initWithCGImage:cgImage size:imageSize]; + CGImageRelease(cgImage); + return image; +#endif +} + @interface MGLMapSnapshotterTests : XCTestCase @property (nonatomic) XCTestExpectation *styleLoadingExpectation; +@property (nonatomic, copy, nullable) void (^runtimeStylingActions)(MGLStyle *style); @end @@ -22,6 +71,7 @@ - (void)tearDown { [MGLAccountManager setAccessToken:nil]; self.styleLoadingExpectation = nil; + self.runtimeStylingActions = nil; [super tearDown]; } @@ -39,9 +89,6 @@ NSImage *blankImage = [NSImage imageWithSize:NSMakeSize(500, 500) flipped:NO drawingHandler:^BOOL(NSRect dstRect) { return YES; }]; - CGImageRef blankCGImage = [blankImage CGImageForProposedRect:nil context:nil hints:nil]; - NSBitmapImageRep *blankImageRep = [[NSBitmapImageRep alloc] initWithCGImage:blankCGImage]; - NSData *blankImageData = [blankImageRep representationUsingType:NSPNGFileType properties:@{}]; #endif NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; @@ -55,15 +102,8 @@ XCTAssertNotNil(snapshotOverlay); if (snapshotOverlay) { XCTAssertNotEqual(snapshotOverlay.context, NULL); -#if TARGET_OS_IPHONE - UIImage *imageFromContext = UIGraphicsGetImageFromCurrentImageContext(); - XCTAssertEqualObjects(UIImagePNGRepresentation(blankImage), UIImagePNGRepresentation(imageFromContext), @"Base map in snapshot should be blank."); -#else - CGImageRef imageFromContext = CGBitmapContextCreateImage(snapshotOverlay.context); - NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:imageFromContext]; - NSData *imageData = [imageRep representationUsingType:NSPNGFileType properties:@{}]; - XCTAssertEqualObjects(blankImageData, imageData, @"Base map in snapshot should be blank."); -#endif + MGLImage *imageFromContext = MGLImageFromCurrentContext(); + XCTAssertTrue(MGLEqualImages(blankImage, imageFromContext), @"Base map in snapshot should be blank."); } [overlayExpectation fulfill]; } completionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { @@ -101,11 +141,71 @@ [self waitForExpectations:@[self.styleLoadingExpectation, completionExpectation] timeout:2 enforceOrder:YES]; } +- (void)testRuntimeStyling { + [self testRuntimeStylingActions:^(MGLStyle *style) { + MGLBackgroundStyleLayer *backgroundLayer = [[MGLBackgroundStyleLayer alloc] initWithIdentifier:@"background"]; + backgroundLayer.backgroundColor = [NSExpression expressionForConstantValue:[MGLColor orangeColor]]; + [style addLayer:backgroundLayer]; + } expectedImageName:@"Fixtures/MGLMapSnapshotterTests/background"]; +} + +/** + Tests that applying the given runtime styling actions on a blank style results in a snapshot image that matches the image with the given name in the asset catalog. + + @param actions Runtime styling actions to apply to the blank style. + @param expectedImageName Name of the test fixture image in Media.xcassets. + */ +- (void)testRuntimeStylingActions:(void (^)(MGLStyle *style))actions expectedImageName:(NSString *)expectedImageName { + self.styleLoadingExpectation = [self expectationWithDescription:@"Style should finish loading."]; + XCTestExpectation *overlayExpectation = [self expectationWithDescription:@"Overlay handler should get called."]; + XCTestExpectation *completionExpectation = [self expectationWithDescription:@"Completion handler should get called."]; + +#if TARGET_OS_IPHONE + UIImage *expectedImage = [UIImage imageNamed:expectedImageName inBundle:[NSBundle bundleForClass:[self class]] compatibleWithTraitCollection:nil]; +#else + NSImage *expectedImage = [[NSBundle bundleForClass:[self class]] imageForResource:expectedImageName]; +#endif + XCTAssertNotNil(expectedImage, @"Image fixture “%@” missing from Media.xcassets.", expectedImageName); + + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; + MGLMapCamera *camera = [MGLMapCamera camera]; + MGLMapSnapshotOptions *options = [[MGLMapSnapshotOptions alloc] initWithStyleURL:styleURL camera:camera size:CGSizeMake(500, 500)]; + + MGLMapSnapshotter *snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; + snapshotter.delegate = self; + self.runtimeStylingActions = actions; + + [snapshotter startWithOverlayHandler:^(MGLMapSnapshotOverlay * _Nonnull snapshotOverlay) { + XCTAssertNotNil(snapshotOverlay); + if (snapshotOverlay) { + XCTAssertNotEqual(snapshotOverlay.context, NULL); + MGLImage *actualImage = MGLImageFromCurrentContext(); + XCTAssertTrue(MGLEqualImages(expectedImage, actualImage), @"Bare snapshot before ornamentation differs from expected image."); + } + [overlayExpectation fulfill]; + } completionHandler:^(MGLMapSnapshot * _Nullable snapshot, NSError * _Nullable error) { + XCTAssertNil(error); + XCTAssertNotNil(snapshot); + if (snapshot) { + XCTAssertEqual(snapshot.image.size.width, 500); + XCTAssertEqual(snapshot.image.size.height, 500); + } + [completionExpectation fulfill]; + }]; + [self waitForExpectations:@[self.styleLoadingExpectation, overlayExpectation, completionExpectation] timeout:2 enforceOrder:YES]; + self.runtimeStylingActions = nil; +} + #pragma mark MGLMapSnapshotterDelegate methods - (void)mapSnapshotter:(MGLMapSnapshotter *)snapshotter didFinishLoadingStyle:(MGLStyle *)style { XCTAssertNotNil(snapshotter); XCTAssertNotNil(style); + + if (self.runtimeStylingActions) { + self.runtimeStylingActions(style); + } + [self.styleLoadingExpectation fulfill]; } diff --git a/platform/darwin/test/Media.xcassets/Fixtures/Contents.json b/platform/darwin/test/Media.xcassets/Fixtures/Contents.json new file mode 100644 index 000000000..38f0c81fc --- /dev/null +++ b/platform/darwin/test/Media.xcassets/Fixtures/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "provides-namespace" : true + } +} \ No newline at end of file diff --git a/platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/Contents.json b/platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/Contents.json new file mode 100644 index 000000000..38f0c81fc --- /dev/null +++ b/platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/Contents.json @@ -0,0 +1,9 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "provides-namespace" : true + } +} \ No newline at end of file diff --git a/platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/background.imageset/Contents.json b/platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/background.imageset/Contents.json new file mode 100644 index 000000000..94b8e3263 --- /dev/null +++ b/platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/background.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "background.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/background.imageset/background.png b/platform/darwin/test/Media.xcassets/Fixtures/MGLMapSnapshotterTests/background.imageset/background.png new file mode 100644 index 0000000000000000000000000000000000000000..4f5f393e87d0e94bf9f7e6beb49650046b3f9376 GIT binary patch literal 20008 zcmeI4y-OTH7{+II#>;wmWG|#R{NfS_NYZEsB!z)Jv!WNcLqtNV#3n+TGzy9#x+#jILS%!dENUjnQEY2cU{sDQH^P+QgAkD8>cJAGootwGaooAl+dEZYn z)u~*zKdY3=m8YXwrA#Y5{ES#C-}aB?q!(wWCRBZQ@I*dDFP@ecDiw8G%D&PcUMibb zk)vM@X_{zdgw4Lvb; z*Wdmaf4y;(FNQUxJ?jVU!thHm;>8BfIiAh&wgf^HXTO>Y6BP`_97No9@e72usvgM3rL1lfmMN$g3p5t zg-M2ncX>o;cyW{9!@vQA_YBt~u1EZ~E=0oVEusVz1IR606$x4u@iw4ULFj`@3W*u| zJj9Kz^Vx@0fmK0Pi#`uQHy|013`hnf1CjyBfMmj_Tpp3wGlE!%dW1