[ios, macos] Snapshot-test runtime styling in snapshots
This commit is contained in:
committed by
Minh Nguyễn
parent
ecf9a141a0
commit
3a55330765
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
@@ -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 |
Reference in New Issue
Block a user