- Update Pod

This commit is contained in:
giuseppenuc committed 2016-03-15 20:02:39 +01:00
1 parent c240fe6257
commit 29704c7514
112 files changed
+1526 -2621

No files matched your search

@@ -0,0 +1,15 @@
framework module FBSnapshotTestCase {
umbrella header "FBSnapshotTestCase.h"
export *
module * { export * }
header "FBSnapshotTestCase.h"
header "FBSnapshotTestCasePlatform.h"
header "FBSnapshotTestController.h"
private header "UIImage+Compare.h"
private header "UIImage+Diff.h"
private header "UIImage+Snapshot.h"
}
@@ -32,6 +32,6 @@
@interface UIImage (Compare)
- (BOOL)compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance;
- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance;
@end
@@ -28,7 +28,7 @@
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "UIImage+Compare.h"
#import <FBSnapshotTestCase/UIImage+Compare.h>
// This makes debugging much more fun
typedef union {
@@ -44,7 +44,7 @@ typedef union {
@implementation UIImage (Compare)
- (BOOL)compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance
- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance
{
NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size.");
@@ -101,7 +101,7 @@ typedef union {
imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0);
} else {
// Go through each pixel in turn and see if it is different
const NSInteger pixelCount = self.size.width * self.size.height;
const NSInteger pixelCount = referenceImageSize.width * referenceImageSize.height;
FBComparePixel *p1 = referenceImagePixels;
FBComparePixel *p2 = imagePixels;
@@ -32,6 +32,6 @@
@interface UIImage (Diff)
- (UIImage *)diffWithImage:(UIImage *)image;
- (UIImage *)fb_diffWithImage:(UIImage *)image;
@end
@@ -28,11 +28,11 @@
// OTHER DEALINGS IN THE SOFTWARE.
//
#import "UIImage+Diff.h"
#import <FBSnapshotTestCase/UIImage+Diff.h>
@implementation UIImage (Diff)
- (UIImage *)diffWithImage:(UIImage *)image
- (UIImage *)fb_diffWithImage:(UIImage *)image
{
if (!image) {
return nil;
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
@interface UIImage (Snapshot)
/// Uses renderInContext: to get a snapshot of the layer.
+ (UIImage *)fb_imageForLayer:(CALayer *)layer;
/// Uses renderInContext: to get a snapshot of the view layer.
+ (UIImage *)fb_imageForViewLayer:(UIView *)view;
/// Uses drawViewHierarchyInRect: to get a snapshot of the view and adds the view into a window if needed.
+ (UIImage *)fb_imageForView:(UIView *)view;
@end
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <FBSnapshotTestCase/UIImage+Snapshot.h>
@implementation UIImage (Snapshot)
+ (UIImage *)fb_imageForLayer:(CALayer *)layer
{
CGRect bounds = layer.bounds;
NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer);
NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer);
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
NSAssert1(context, @"Could not generate context for layer %@", layer);
CGContextSaveGState(context);
[layer layoutIfNeeded];
[layer renderInContext:context];
CGContextRestoreGState(context);
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshot;
}
+ (UIImage *)fb_imageForViewLayer:(UIView *)view
{
[view layoutIfNeeded];
return [self fb_imageForLayer:view.layer];
}
+ (UIImage *)fb_imageForView:(UIView *)view
{
CGRect bounds = view.bounds;
NSAssert1(CGRectGetWidth(bounds), @"Zero width for view %@", view);
NSAssert1(CGRectGetHeight(bounds), @"Zero height for view %@", view);
UIWindow *window = view.window;
if (window == nil) {
window = [[UIWindow alloc] initWithFrame:bounds];
[window addSubview:view];
[window makeKeyAndVisible];
}
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
[view layoutIfNeeded];
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES];
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshot;
}
@end
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, Facebook, Inc.
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -16,6 +16,21 @@
#import <XCTest/XCTest.h>
/*
There are three ways of setting reference image directories.
1. Set the preprocessor macro FB_REFERENCE_IMAGE_DIR to a double quoted
c-string with the path.
2. Set an environment variable named FB_REFERENCE_IMAGE_DIR with the path. This
takes precedence over the preprocessor macro to allow for run-time override.
3. Keep everything unset, which will cause the reference images to be looked up
inside the bundle holding the current test, in the
Resources/ReferenceImages_* directories.
*/
#ifndef FB_REFERENCE_IMAGE_DIR
#define FB_REFERENCE_IMAGE_DIR ""
#endif
/**
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
@param view The view to snapshot
@@ -24,25 +39,11 @@
@param tolerance The percentage of pixels that can differ and still count as an 'identical' view
*/
#define FBSnapshotVerifyViewWithOptions(view__, identifier__, suffixes__, tolerance__) \
{ \
NSString *envReferenceImageDirectory = [NSProcessInfo processInfo].environment[@"FB_REFERENCE_IMAGE_DIR"]; \
NSError *error__ = nil; \
BOOL comparisonSuccess__; \
XCTAssertTrue((suffixes__.count > 0), @"Suffixes set cannot be empty %@", suffixes__); \
XCTAssertNotNil(envReferenceImageDirectory, @"Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.");\
for (NSString *suffix__ in suffixes__) { \
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%@%@", envReferenceImageDirectory, suffix__]; \
comparisonSuccess__ = [self compareSnapshotOfView:(view__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) tolerance:(tolerance__) error:&error__]; \
if (comparisonSuccess__ || self.recordMode) break; \
} \
XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \
XCTAssertFalse(self.recordMode, @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!"); \
}
FBSnapshotVerifyViewOrLayerWithOptions(View, view__, identifier__, suffixes__, tolerance__)
#define FBSnapshotVerifyView(view__, identifier__) \
{ \
FBSnapshotVerifyViewWithOptions(view__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0); \
}
FBSnapshotVerifyViewWithOptions(view__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0)
/**
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
@@ -52,32 +53,64 @@ FBSnapshotVerifyViewWithOptions(view__, identifier__, FBSnapshotTestCaseDefaultS
@param tolerance The percentage of pixels that can differ and still count as an 'identical' layer
*/
#define FBSnapshotVerifyLayerWithOptions(layer__, identifier__, suffixes__, tolerance__) \
{ \
NSString *envReferenceImageDirectory = [NSProcessInfo processInfo].environment[@"FB_REFERENCE_IMAGE_DIR"]; \
NSError *error__ = nil; \
BOOL comparisonSuccess__; \
XCTAssertTrue((suffixes__.count > 0), @"Suffixes set cannot be empty %@", suffixes__); \
XCTAssertNotNil(envReferenceImageDirectory, @"Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.");\
for (NSString *suffix__ in suffixes__) { \
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%@%@", envReferenceImageDirectory, suffix__]; \
comparisonSuccess__ = [self compareSnapshotOfLayer:(layer__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) tolerance:(tolerance__) error:&error__]; \
if (comparisonSuccess__ || self.recordMode) break; \
} \
XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \
XCTAssertFalse(self.recordMode, @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!"); \
}
FBSnapshotVerifyViewOrLayerWithOptions(Layer, layer__, identifier__, suffixes__, tolerance__)
#define FBSnapshotVerifyLayer(layer__, identifier__) \
FBSnapshotVerifyLayerWithOptions(layer__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0)
#define FBSnapshotVerifyViewOrLayerWithOptions(what__, viewOrLayer__, identifier__, suffixes__, tolerance__) \
{ \
FBSnapshotVerifyLayerWithOptions(layer__, identifier__, FBSnapshotTestCaseDefaultSuffixes(), 0); \
NSString *referenceImageDirectory = [self getReferenceImageDirectoryWithDefault:(@ FB_REFERENCE_IMAGE_DIR)]; \
XCTAssertNotNil(referenceImageDirectory, @"Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.");\
XCTAssertTrue((suffixes__.count > 0), @"Suffixes set cannot be empty %@", suffixes__); \
\
BOOL testSuccess__ = NO; \
NSError *error__ = nil; \
NSMutableArray *errors__ = [NSMutableArray array]; \
\
if (self.recordMode) { \
\
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffixes__.firstObject]; \
BOOL referenceImageSaved__ = [self compareSnapshotOf ## what__ :(viewOrLayer__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) tolerance:(tolerance__) error:&error__]; \
if (!referenceImageSaved__) { \
[errors__ addObject:error__]; \
} \
} else { \
\
for (NSString *suffix__ in suffixes__) { \
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%@%@", referenceImageDirectory, suffix__]; \
BOOL referenceImageAvailable = [self referenceImageRecordedInDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \
\
if (referenceImageAvailable) { \
BOOL comparisonSuccess__ = [self compareSnapshotOf ## what__ :(viewOrLayer__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) tolerance:(tolerance__) error:&error__]; \
[errors__ removeAllObjects]; \
if (comparisonSuccess__) { \
testSuccess__ = YES; \
break; \
} else { \
[errors__ addObject:error__]; \
} \
} else { \
[errors__ addObject:error__]; \
} \
} \
} \
XCTAssertTrue(testSuccess__, @"Snapshot comparison failed: %@", errors__.firstObject); \
XCTAssertFalse(self.recordMode, @"Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!"); \
}
/**
The base class of view snapshotting tests. If you have small UI component, it's often easier to configure it in a test
and compare an image of the view to a reference image that write lots of complex layout-code tests.
In order to flip the tests in your subclass to record the reference images set @c recordMode to @c YES.
@attention When recording, the reference image directory should be explicitly
set, otherwise the images may be written to somewhere inside the
simulator directory.
For example:
@code
- (void)setUp
@@ -94,6 +127,26 @@ FBSnapshotVerifyLayerWithOptions(layer__, identifier__, FBSnapshotTestCaseDefaul
*/
@property (readwrite, nonatomic, assign) BOOL recordMode;
/**
When @c YES appends the name of the device model and OS to the snapshot file name.
The default value is @c NO.
*/
@property (readwrite, nonatomic, assign, getter=isDeviceAgnostic) BOOL deviceAgnostic;
/**
When YES, renders a snapshot of the complete view hierarchy as visible onscreen.
There are several things that do not work if renderInContext: is used.
- UIVisualEffect #70
- UIAppearance #91
- Size Classes #92
@attention If the view does't belong to a UIWindow, it will create one and add the view as a subview.
*/
@property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect;
- (void)setUp NS_REQUIRES_SUPER;
- (void)tearDown NS_REQUIRES_SUPER;
/**
Performs the comparison or records a snapshot of the layer if recordMode is YES.
@param layer The Layer to snapshot
@@ -124,4 +177,24 @@ FBSnapshotVerifyLayerWithOptions(layer__, identifier__, FBSnapshotTestCaseDefaul
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr;
/**
Checks if reference image with identifier based name exists in the reference images directory.
@param referenceImagesDirectory The directory in which reference images are stored.
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if reference image exists.
*/
- (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Returns the reference image directory.
Helper function used to implement the assert macros.
@param dir directory to use if environment variable not specified. Ignored if null or empty.
*/
- (NSString *)getReferenceImageDirectoryWithDefault:(NSString *)dir;
@end
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, Facebook, Inc.
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -8,15 +8,16 @@
*
*/
#import "FBSnapshotTestCase.h"
#import "FBSnapshotTestController.h"
#import <FBSnapshotTestCase/FBSnapshotTestCase.h>
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
@implementation FBSnapshotTestCase
{
FBSnapshotTestController *_snapshotController;
}
#pragma mark - Overrides
- (void)setUp
{
[super setUp];
@@ -40,6 +41,30 @@
_snapshotController.recordMode = recordMode;
}
- (BOOL)isDeviceAgnostic
{
return _snapshotController.deviceAgnostic;
}
- (void)setDeviceAgnostic:(BOOL)deviceAgnostic
{
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
_snapshotController.deviceAgnostic = deviceAgnostic;
}
- (BOOL)usesDrawViewHierarchyInRect
{
return _snapshotController.usesDrawViewHierarchyInRect;
}
- (void)setUsesDrawViewHierarchyInRect:(BOOL)usesDrawViewHierarchyInRect
{
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
_snapshotController.usesDrawViewHierarchyInRect = usesDrawViewHierarchyInRect;
}
#pragma mark - Public API
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
@@ -66,8 +91,33 @@
error:errorPtr];
}
#pragma mark -
#pragma mark Private API
- (BOOL)referenceImageRecordedInDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
NSAssert1(_snapshotController, @"%s cannot be called before [super setUp]", __FUNCTION__);
_snapshotController.referenceImagesDirectory = referenceImagesDirectory;
UIImage *referenceImage = [_snapshotController referenceImageForSelector:self.invocation.selector
identifier:identifier
error:errorPtr];
return (referenceImage != nil);
}
- (NSString *)getReferenceImageDirectoryWithDefault:(NSString *)dir
{
NSString *envReferenceImageDirectory = [NSProcessInfo processInfo].environment[@"FB_REFERENCE_IMAGE_DIR"];
if (envReferenceImageDirectory) {
return envReferenceImageDirectory;
}
if (dir && dir.length > 0) {
return dir;
}
return [[NSBundle bundleForClass:self.class].resourcePath stringByAppendingPathComponent:@"ReferenceImages"];
}
#pragma mark - Private API
- (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
@@ -30,6 +30,14 @@ BOOL FBSnapshotTestCaseIs64Bit(void);
@returns An @c NSOrderedSet object containing strings that are appended to the reference images directory.
*/
NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void);
/**
Returns a fully «normalized» file name.
Strips punctuation and spaces and replaces them with @c _. Also appends the device model, running OS and screen size to the file name.
@returns An @c NSString object containing the passed @c fileName with the device model, OS and screen size appended at the end.
*/
NSString *FBDeviceAgnosticNormalizedFileName(NSString *fileName);
#ifdef __cplusplus
}
@@ -9,6 +9,7 @@
*/
#import <FBSnapshotTestCase/FBSnapshotTestCasePlatform.h>
#import <UIKit/UIKit.h>
BOOL FBSnapshotTestCaseIs64Bit(void)
{
@@ -29,3 +30,20 @@ NSOrderedSet *FBSnapshotTestCaseDefaultSuffixes(void)
}
return [suffixesSet copy];
}
NSString *FBDeviceAgnosticNormalizedFileName(NSString *fileName)
{
UIDevice *device = [UIDevice currentDevice];
CGSize screenSize = [[UIApplication sharedApplication] keyWindow].bounds.size;
NSString *os = device.systemVersion;
fileName = [NSString stringWithFormat:@"%@_%@%@_%.0fx%.0f", fileName, device.model, os, screenSize.width, screenSize.height];
NSMutableCharacterSet *invalidCharacters = [NSMutableCharacterSet new];
[invalidCharacters formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
[invalidCharacters formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
NSArray *validComponents = [fileName componentsSeparatedByCharactersInSet:invalidCharacters];
fileName = [validComponents componentsJoinedByString:@"_"];
return fileName;
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, Facebook, Inc.
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -37,9 +37,25 @@ extern NSString *const FBReferenceImageFilePathKey;
/**
Record snapshots.
**/
*/
@property (readwrite, nonatomic, assign) BOOL recordMode;
/**
When @c YES appends the name of the device model and OS to the snapshot file name.
The default value is @c NO.
*/
@property (readwrite, nonatomic, assign, getter=isDeviceAgnostic) BOOL deviceAgnostic;
/**
Uses drawViewHierarchyInRect:afterScreenUpdates: to draw the image instead of renderInContext:
*/
@property (readwrite, nonatomic, assign) BOOL usesDrawViewHierarchyInRect;
/**
The directory in which referfence images are stored.
*/
@property (readwrite, nonatomic, copy) NSString *referenceImagesDirectory;
/**
@param testClass The subclass of FBSnapshotTestCase that is using this controller.
@returns An instance of FBSnapshotTestController.
@@ -53,7 +69,6 @@ extern NSString *const FBReferenceImageFilePathKey;
*/
- (instancetype)initWithTestName:(NSString *)testName;
/**
Performs the comparison of the layer.
@param layer The Layer to snapshot.
@@ -95,12 +110,6 @@ extern NSString *const FBReferenceImageFilePathKey;
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr;
/**
The directory in which referfence images are stored.
*/
@property (readwrite, nonatomic, copy) NSString *referenceImagesDirectory;
/**
Loads a reference image.
@param selector The test method being run.
@@ -112,18 +121,6 @@ extern NSString *const FBReferenceImageFilePathKey;
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Saves a reference image.
@param selector The test method being run.
@param identifier The optional identifier, used when multiple images are tested in a single -test method.
@param errorPtr An error, if this methods returns NO, the error will be something useful.
@returns An image.
*/
- (BOOL)saveReferenceImage:(UIImage *)image
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr;
/**
Performs a pixel-by-pixel comparison of the two images with an allowable margin of error.
@param referenceImage The reference (correct) image.
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, Facebook, Inc.
* Copyright (c) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -8,25 +8,31 @@
*
*/
#import "FBSnapshotTestController.h"
#import "UIImage+Compare.h"
#import "UIImage+Diff.h"
#import <FBSnapshotTestCase/FBSnapshotTestController.h>
#import <FBSnapshotTestCase/FBSnapshotTestCasePlatform.h>
#import <FBSnapshotTestCase/UIImage+Compare.h>
#import <FBSnapshotTestCase/UIImage+Diff.h>
#import <FBSnapshotTestCase/UIImage+Snapshot.h>
#import <UIKit/UIKit.h>
NSString *const FBSnapshotTestControllerErrorDomain = @"FBSnapshotTestControllerErrorDomain";
NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
FBTestSnapshotFileNameTypeReference,
FBTestSnapshotFileNameTypeFailedReference,
FBTestSnapshotFileNameTypeFailedTest,
FBTestSnapshotFileNameTypeFailedTestDiff,
};
@implementation FBSnapshotTestController
{
NSString *_testName;
NSFileManager *_fileManager;
}
#pragma mark -
#pragma mark Lifecycle
#pragma mark - Initializers
- (instancetype)initWithTestClass:(Class)testClass;
{
@@ -35,23 +41,60 @@ NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
- (instancetype)initWithTestName:(NSString *)testName
{
if ((self = [super init])) {
if (self = [super init]) {
_testName = [testName copy];
_deviceAgnostic = NO;
_fileManager = [[NSFileManager alloc] init];
}
return self;
}
#pragma mark -
#pragma mark Properties
#pragma mark - Overrides
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ %@", [super description], _referenceImagesDirectory];
}
#pragma mark -
#pragma mark Public API
#pragma mark - Public API
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self compareSnapshotOfViewOrLayer:layer
selector:selector
identifier:identifier
tolerance:0
error:errorPtr];
}
- (BOOL)compareSnapshotOfView:(UIView *)view
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self compareSnapshotOfViewOrLayer:view
selector:selector
identifier:identifier
tolerance:0
error:errorPtr];
}
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
if (self.recordMode) {
return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
} else {
return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance error:errorPtr];
}
}
- (UIImage *)referenceImageForSelector:(SEL)selector
identifier:(NSString *)identifier
@@ -78,42 +121,33 @@ NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
return image;
}
- (BOOL)saveReferenceImage:(UIImage *)image
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
- (BOOL)compareReferenceImage:(UIImage *)referenceImage
toImage:(UIImage *)image
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
BOOL didWrite = NO;
if (nil != image) {
NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier];
NSData *pngData = UIImagePNGRepresentation(image);
if (nil != pngData) {
NSError *creationError = nil;
BOOL didCreateDir = [_fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:&creationError];
if (!didCreateDir) {
if (NULL != errorPtr) {
*errorPtr = creationError;
}
return NO;
}
didWrite = [pngData writeToFile:filePath options:NSDataWritingAtomic error:errorPtr];
if (didWrite) {
NSLog(@"Reference image save at: %@", filePath);
}
} else {
if (nil != errorPtr) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodePNGCreationFailed
userInfo:@{
FBReferenceImageFilePathKey: filePath,
}];
}
if (CGSizeEqualToSize(referenceImage.size, image.size)) {
BOOL imagesEqual = [referenceImage fb_compareWithImage:image tolerance:tolerance];
if (NULL != errorPtr) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodeImagesDifferent
userInfo:@{
NSLocalizedDescriptionKey: @"Images different",
}];
}
return imagesEqual;
}
return didWrite;
if (NULL != errorPtr) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodeImagesDifferentSizes
userInfo:@{
NSLocalizedDescriptionKey: @"Images different sizes",
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"referenceImage:%@, image:%@",
NSStringFromCGSize(referenceImage.size),
NSStringFromCGSize(image.size)],
}];
}
return NO;
}
- (BOOL)saveFailedReferenceImage:(UIImage *)referenceImage
@@ -157,7 +191,7 @@ NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
identifier:identifier
fileNameType:FBTestSnapshotFileNameTypeFailedTestDiff];
UIImage *diffImage = [referenceImage diffWithImage:testImage];
UIImage *diffImage = [referenceImage fb_diffWithImage:testImage];
NSData *diffImageData = UIImagePNGRepresentation(diffImage);
if (![diffImageData writeToFile:diffPath options:NSDataWritingAtomic error:errorPtr]) {
@@ -170,44 +204,7 @@ NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
return YES;
}
- (BOOL)compareReferenceImage:(UIImage *)referenceImage
toImage:(UIImage *)image
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr {
if (CGSizeEqualToSize(referenceImage.size, image.size)) {
BOOL imagesEqual = [referenceImage compareWithImage:image tolerance:tolerance];
if (NULL != errorPtr) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodeImagesDifferent
userInfo:@{
NSLocalizedDescriptionKey: @"Images different",
}];
}
return imagesEqual;
}
if (NULL != errorPtr) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodeImagesDifferentSizes
userInfo:@{
NSLocalizedDescriptionKey: @"Images different sizes",
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"referenceImage:%@, image:%@",
NSStringFromCGSize(referenceImage.size),
NSStringFromCGSize(image.size)],
}];
}
return NO;
}
#pragma mark -
#pragma mark Private API
typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
FBTestSnapshotFileNameTypeReference,
FBTestSnapshotFileNameTypeFailedReference,
FBTestSnapshotFileNameTypeFailedTest,
FBTestSnapshotFileNameTypeFailedTestDiff,
};
#pragma mark - Private API
- (NSString *)_fileNameForSelector:(SEL)selector
identifier:(NSString *)identifier
@@ -232,6 +229,11 @@ typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
if (0 < identifier.length) {
fileName = [fileName stringByAppendingFormat:@"_%@", identifier];
}
if (self.isDeviceAgnostic) {
fileName = FBDeviceAgnosticNormalizedFileName(fileName);
}
if ([[UIScreen mainScreen] scale] > 1) {
fileName = [fileName stringByAppendingFormat:@"@%.fx", [[UIScreen mainScreen] scale]];
}
@@ -239,7 +241,8 @@ typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
return fileName;
}
- (NSString *)_referenceFilePathForSelector:(SEL)selector identifier:(NSString *)identifier
- (NSString *)_referenceFilePathForSelector:(SEL)selector
identifier:(NSString *)identifier
{
NSString *fileName = [self _fileNameForSelector:selector
identifier:identifier
@@ -265,46 +268,6 @@ typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
return filePath;
}
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self compareSnapshotOfViewOrLayer:layer
selector:selector
identifier:identifier
tolerance:0
error:errorPtr];
}
- (BOOL)compareSnapshotOfView:(UIView *)view
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
return [self compareSnapshotOfViewOrLayer:view
selector:selector
identifier:identifier
tolerance:0
error:errorPtr];
}
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
if (self.recordMode) {
return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
} else {
return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance error:errorPtr];
}
}
#pragma mark -
#pragma mark Private API
- (BOOL)_performPixelComparisonWithViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
@@ -313,7 +276,7 @@ typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
{
UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr];
if (nil != referenceImage) {
UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer];
UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer];
BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance error:errorPtr];
if (!imagesSame) {
[self saveFailedReferenceImage:referenceImage
@@ -332,53 +295,62 @@ typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer];
return [self saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr];
UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer];
return [self _saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr];
}
- (UIImage *)_snapshotViewOrLayer:(id)viewOrLayer
- (BOOL)_saveReferenceImage:(UIImage *)image
selector:(SEL)selector
identifier:(NSString *)identifier
error:(NSError **)errorPtr
{
BOOL didWrite = NO;
if (nil != image) {
NSString *filePath = [self _referenceFilePathForSelector:selector identifier:identifier];
NSData *pngData = UIImagePNGRepresentation(image);
if (nil != pngData) {
NSError *creationError = nil;
BOOL didCreateDir = [_fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:&creationError];
if (!didCreateDir) {
if (NULL != errorPtr) {
*errorPtr = creationError;
}
return NO;
}
didWrite = [pngData writeToFile:filePath options:NSDataWritingAtomic error:errorPtr];
if (didWrite) {
NSLog(@"Reference image save at: %@", filePath);
}
} else {
if (nil != errorPtr) {
*errorPtr = [NSError errorWithDomain:FBSnapshotTestControllerErrorDomain
code:FBSnapshotTestControllerErrorCodePNGCreationFailed
userInfo:@{
FBReferenceImageFilePathKey: filePath,
}];
}
}
}
return didWrite;
}
- (UIImage *)_imageForViewOrLayer:(id)viewOrLayer
{
CALayer *layer = nil;
if ([viewOrLayer isKindOfClass:[UIView class]]) {
return [self _renderView:viewOrLayer];
if (_usesDrawViewHierarchyInRect) {
return [UIImage fb_imageForView:viewOrLayer];
} else {
return [UIImage fb_imageForViewLayer:viewOrLayer];
}
} else if ([viewOrLayer isKindOfClass:[CALayer class]]) {
layer = (CALayer *)viewOrLayer;
[layer layoutIfNeeded];
return [self _renderLayer:layer];
return [UIImage fb_imageForLayer:viewOrLayer];
} else {
[NSException raise:@"Only UIView and CALayer classes can be snapshotted" format:@"%@", viewOrLayer];
}
return nil;
}
- (UIImage *)_renderLayer:(CALayer *)layer
{
CGRect bounds = layer.bounds;
NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer);
NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer);
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
NSAssert1(context, @"Could not generate context for layer %@", layer);
CGContextSaveGState(context);
{
[layer renderInContext:context];
}
CGContextRestoreGState(context);
UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshot;
}
- (UIImage *)_renderView:(UIView *)view
{
[view layoutIfNeeded];
return [self _renderLayer:view.layer];
}
@end
@@ -10,40 +10,48 @@
public extension FBSnapshotTestCase {
public func FBSnapshotVerifyView(view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), file: String = __FILE__, line: UInt = __LINE__) {
let envReferenceImageDirectory = NSProcessInfo.processInfo().environment["FB_REFERENCE_IMAGE_DIR"] as? String
var error: NSError?
if let envReferenceImageDirectory = envReferenceImageDirectory {
for suffix in suffixes {
let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)"
let comparisonSuccess = compareSnapshotOfView(view, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: 0, error: &error)
if comparisonSuccess || recordMode {
break
}
assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line)
assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line)
}
} else {
assert(false, message: "Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.", file: file, line: line)
}
FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes)
}
public func FBSnapshotVerifyLayer(layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), file: String = __FILE__, line: UInt = __LINE__) {
let envReferenceImageDirectory = NSProcessInfo.processInfo().environment["FB_REFERENCE_IMAGE_DIR"] as? String
FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes)
}
private func FBSnapshotVerifyViewOrLayer(viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), file: String = __FILE__, line: UInt = __LINE__) {
let envReferenceImageDirectory = self.getReferenceImageDirectoryWithDefault(FB_REFERENCE_IMAGE_DIR)
var error: NSError?
var comparisonSuccess = false
if let envReferenceImageDirectory = envReferenceImageDirectory {
for suffix in suffixes {
let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)"
comparisonSuccess = compareSnapshotOfLayer(layer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: 0, error: &error)
if viewOrLayer.isKindOfClass(UIView) {
do {
try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: 0)
comparisonSuccess = true
} catch let error1 as NSError {
error = error1
comparisonSuccess = false
}
} else if viewOrLayer.isKindOfClass(CALayer) {
do {
try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: 0)
comparisonSuccess = true
} catch let error1 as NSError {
error = error1
comparisonSuccess = false
}
} else {
assertionFailure("Only UIView and CALayer classes can be snapshotted")
}
assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line)
if comparisonSuccess || recordMode {
break
}
assert(comparisonSuccess, message: "Snapshot comparison failed: \(error)", file: file, line: line)
assert(recordMode == false, message: "Test ran in record mode. Reference image is now saved. Disable record mode to perform an actual snapshot comparison!", file: file, line: line)
}
} else {
XCTFail("Missing value for referenceImagesDirectory - Set FB_REFERENCE_IMAGE_DIR as Environment variable in your scheme.")
+3 -3
View File
@@ -44,9 +44,7 @@ Installation with CocoaPods
Replace "Tests" with the name of your test project.
2. Define `FB_REFERENCE_IMAGE_DIR` in your scheme. This should
point to the directory where you want reference images to be stored. At Facebook,
we normally use this:
2. There are [three ways](https://github.com/facebook/ios-snapshot-test-case/blob/master/FBSnapshotTestCase/FBSnapshotTestCase.h#L19-L29) of setting reference image directories, the recommended one is to define `FB_REFERENCE_IMAGE_DIR` in your scheme. This should point to the directory where you want reference images to be stored. At Facebook, we normally use this:
|Name|Value|
|:---|:----|
@@ -75,6 +73,8 @@ Features
- Supply an optional "identifier" if you want to perform multiple snapshots
in a single test method.
- Support for `CALayer` via `FBSnapshotVerifyLayer`.
- `usesDrawViewHierarchyInRect` to handle cases like `UIVisualEffect`, `UIAppearance` and Size Classes.
- `isDeviceAgnostic` to allow appending the device model (`iPhone`, `iPad`, `iPod Touch`, etc), OS version and screen size to the images (allowing to have multiple tests for the same «snapshot» for different `OS`s and devices).
Notes
-----