[ios, macos] Snapshot-test runtime styling in snapshots

This commit is contained in:
Minh Nguyễn
2020-03-23 19:08:27 -07:00
committed by Minh Nguyễn
parent ecf9a141a0
commit 3a55330765
5 changed files with 151 additions and 12 deletions

View File

@@ -1,9 +1,58 @@
#import <Mapbox/Mapbox.h>
#import <XCTest/XCTest.h>
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 <MGLMapSnapshotterDelegate>
@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];
}

View File

@@ -0,0 +1,9 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"provides-namespace" : true
}
}

View File

@@ -0,0 +1,9 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"provides-namespace" : true
}
}

View File

@@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB