Using `drawViewHierarchyInRect` instead of `renderInContext` for snapshot tests
Summary: We found that `-[CALayer renderInContext:]` produces bad results in some cases (which is actually documented thing!), so we decided to replace it with `-[UIView drawViewHierarchyInRect:]` which is more reliable (I hope). As part of this change I completly removed support of `CALayer` from local fork of `RNTesterIntegrationTests`. See https://github.com/facebook/react-native/pull/14011#issuecomment-303844580 for more details. janicduplessis Reviewed By: javache Differential Revision: D5129492 fbshipit-source-id: 6a9227037c85bb8f862d55267f5301e177985ad9
|
@ -18,44 +18,6 @@
|
|||
#define FB_REFERENCE_IMAGE_DIR "\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\""
|
||||
#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
|
||||
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
|
||||
@param referenceImageDirectorySuffix An optional suffix, appended to the reference image directory path, such as "_iOS8"
|
||||
*/
|
||||
#define FBSnapshotVerifyViewWithReferenceDirectorySuffix(view__, identifier__, referenceImagesDirectorySuffix__) \
|
||||
{ \
|
||||
NSError *error__ = nil; \
|
||||
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%s%@", FB_REFERENCE_IMAGE_DIR, referenceImagesDirectorySuffix__]; \
|
||||
BOOL comparisonSuccess__ = [self compareSnapshotOfView:(view__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \
|
||||
XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \
|
||||
}
|
||||
|
||||
#define FBSnapshotVerifyView(view__, identifier__) \
|
||||
{ \
|
||||
FBSnapshotVerifyViewWithReferenceDirectorySuffix(view__, identifier__, @""); \
|
||||
}
|
||||
|
||||
/**
|
||||
Similar to our much-loved XCTAssert() macros. Use this to perform your test. No need to write an explanation, though.
|
||||
@param layer The layer to snapshot
|
||||
@param identifier An optional identifier, used is there are multiple snapshot tests in a given -test method.
|
||||
@param referenceImageDirectorySuffix An optional suffix, appended to the reference image directory path, such as "_iOS8"
|
||||
*/
|
||||
#define FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, referenceImagesDirectorySuffix__) \
|
||||
{ \
|
||||
NSError *error__ = nil; \
|
||||
NSString *referenceImagesDirectory__ = [NSString stringWithFormat:@"%s%@", FB_REFERENCE_IMAGE_DIR, referenceImagesDirectorySuffix__]; \
|
||||
BOOL comparisonSuccess__ = [self compareSnapshotOfLayer:(layer__) referenceImagesDirectory:referenceImagesDirectory__ identifier:(identifier__) error:&error__]; \
|
||||
XCTAssertTrue(comparisonSuccess__, @"Snapshot comparison failed: %@", error__); \
|
||||
}
|
||||
|
||||
#define FBSnapshotVerifyLayer(layer__, identifier__) \
|
||||
{ \
|
||||
FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, @""); \
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
|
@ -70,19 +32,6 @@ FBSnapshotVerifyLayerWithReferenceDirectorySuffix(layer__, identifier__, @""); \
|
|||
*/
|
||||
@property (readwrite, nonatomic, assign) BOOL recordMode;
|
||||
|
||||
/**
|
||||
Performs the comparisons or records a snapshot of the layer if recordMode is YES.
|
||||
@param layer The Layer to snapshot
|
||||
@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 error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
|
||||
@returns YES if the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs the comparisons or records a snapshot of the view if recordMode is YES.
|
||||
@param view The view to snapshot
|
||||
|
|
|
@ -42,41 +42,16 @@
|
|||
self.snapshotController.recordMode = recordMode;
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self _compareSnapshotOfViewOrLayer:layer
|
||||
referenceImagesDirectory:referenceImagesDirectory
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self _compareSnapshotOfViewOrLayer:view
|
||||
referenceImagesDirectory:referenceImagesDirectory
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Private API
|
||||
|
||||
- (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
referenceImagesDirectory:(NSString *)referenceImagesDirectory
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
_snapshotController.referenceImagesDirectory = referenceImagesDirectory;
|
||||
return [_snapshotController compareSnapshotOfViewOrLayer:viewOrLayer
|
||||
selector:self.invocation.selector
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
return [_snapshotController compareSnapshotOfView:view
|
||||
selector:self.invocation.selector
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -41,7 +41,7 @@ extern NSString *const FBReferenceImageFilePathKey;
|
|||
@property(readwrite, nonatomic, assign) BOOL recordMode;
|
||||
|
||||
/**
|
||||
@param testClass The subclass of FBSnapshotTestCase that is using this controller.d.
|
||||
@param testClass The subclass of FBSnapshotTestCase that is using this controller.
|
||||
@returns An instance of FBSnapshotTestController.
|
||||
*/
|
||||
- (id)initWithTestClass:(Class)testClass;
|
||||
|
@ -53,19 +53,6 @@ extern NSString *const FBReferenceImageFilePathKey;
|
|||
*/
|
||||
- (id)initWithTestName:(NSString *)testName;
|
||||
|
||||
|
||||
/**
|
||||
Performs the comparison of the layer.
|
||||
@param layer The Layer to snapshot.
|
||||
@param identifier An optional identifier, used is there are muliptle 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 the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs the comparison of the view.
|
||||
@param view The view to snapshot.
|
||||
|
@ -79,20 +66,6 @@ extern NSString *const FBReferenceImageFilePathKey;
|
|||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
Performs the comparison of a view or layer.
|
||||
@param viewOrLayer The view or layer to snapshot.
|
||||
@param selector selector
|
||||
@param identifier An optional identifier, used is there are muliptle 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 the comparison (or saving of the reference image) succeeded.
|
||||
*/
|
||||
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr;
|
||||
|
||||
|
||||
/**
|
||||
The directory in which reference images are stored.
|
||||
*/
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
|
||||
#import "FBSnapshotTestController.h"
|
||||
|
||||
#import "UIImage+Compare.h"
|
||||
#import "UIImage+Diff.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "UIImage+Compare.h"
|
||||
#import "UIImage+Diff.h"
|
||||
|
||||
NSString *const FBSnapshotTestControllerErrorDomain = @"FBSnapshotTestControllerErrorDomain";
|
||||
|
||||
NSString *const FBReferenceImageFilePathKey = @"FBReferenceImageFilePathKey";
|
||||
|
@ -39,15 +39,14 @@ typedef struct RGBAPixel {
|
|||
NSFileManager *_fileManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Lifecycle
|
||||
#pragma mark - Lifecycle
|
||||
|
||||
- (id)initWithTestClass:(Class)testClass;
|
||||
- (instancetype)initWithTestClass:(Class)testClass;
|
||||
{
|
||||
return [self initWithTestName:NSStringFromClass(testClass)];
|
||||
}
|
||||
|
||||
- (id)initWithTestName:(NSString *)testName
|
||||
- (instancetype)initWithTestName:(NSString *)testName
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_testName = [testName copy];
|
||||
|
@ -56,16 +55,14 @@ typedef struct RGBAPixel {
|
|||
return self;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Properties
|
||||
#pragma mark - Properties
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"%@ %@", [super description], _referenceImagesDirectory];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Public API
|
||||
#pragma mark - Public API
|
||||
|
||||
- (UIImage *)referenceImageForSelector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
|
@ -211,8 +208,7 @@ typedef struct RGBAPixel {
|
|||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Private API
|
||||
#pragma mark - Private API
|
||||
|
||||
typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
|
||||
FBTestSnapshotFileNameTypeReference,
|
||||
|
@ -280,51 +276,28 @@ 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
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfView:(UIView *)view
|
||||
- (BOOL)compareSnapshotOfView:(id)view
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
return [self compareSnapshotOfViewOrLayer:view
|
||||
selector:selector
|
||||
identifier:identifier
|
||||
error:errorPtr];
|
||||
}
|
||||
|
||||
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
if (self.recordMode) {
|
||||
return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
|
||||
return [self _recordSnapshotOfView:view selector:selector identifier:identifier error:errorPtr];
|
||||
} else {
|
||||
return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
|
||||
return [self _performPixelComparisonWithView:view selector:selector identifier:identifier error:errorPtr];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Private API
|
||||
#pragma mark - Private API
|
||||
|
||||
- (BOOL)_performPixelComparisonWithViewOrLayer:(UIView *)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
- (BOOL)_performPixelComparisonWithView:(UIView *)view
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr];
|
||||
if (nil != referenceImage) {
|
||||
UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer];
|
||||
UIImage *snapshot = [self _snapshotView:view];
|
||||
BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot error:errorPtr];
|
||||
if (!imagesSame) {
|
||||
[self saveFailedReferenceImage:referenceImage
|
||||
|
@ -338,46 +311,33 @@ typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
|
|||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)_recordSnapshotOfViewOrLayer:(id)viewOrLayer
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
- (BOOL)_recordSnapshotOfView:(UIView *)view
|
||||
selector:(SEL)selector
|
||||
identifier:(NSString *)identifier
|
||||
error:(NSError **)errorPtr
|
||||
{
|
||||
UIImage *snapshot = [self _snapshotViewOrLayer:viewOrLayer];
|
||||
UIImage *snapshot = [self _snapshotView:view];
|
||||
return [self saveReferenceImage:snapshot selector:selector identifier:identifier error:errorPtr];
|
||||
}
|
||||
|
||||
- (UIImage *)_snapshotViewOrLayer:(id)viewOrLayer
|
||||
- (UIImage *)_snapshotView:(UIView *)view
|
||||
{
|
||||
CALayer *layer = nil;
|
||||
[view layoutIfNeeded];
|
||||
|
||||
if ([viewOrLayer isKindOfClass:[UIView class]]) {
|
||||
return [self _renderView:viewOrLayer];
|
||||
} else if ([viewOrLayer isKindOfClass:[CALayer class]]) {
|
||||
layer = (CALayer *)viewOrLayer;
|
||||
[layer layoutIfNeeded];
|
||||
return [self _renderLayer:layer];
|
||||
} else {
|
||||
[NSException raise:@"Only UIView and CALayer classes can be snapshotted" format:@"%@", viewOrLayer];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
CGRect bounds = view.bounds;
|
||||
|
||||
- (UIImage *)_renderLayer:(CALayer *)layer
|
||||
{
|
||||
CGRect bounds = layer.bounds;
|
||||
|
||||
NSAssert1(CGRectGetWidth(bounds), @"Zero width for layer %@", layer);
|
||||
NSAssert1(CGRectGetHeight(bounds), @"Zero height for layer %@", layer);
|
||||
NSAssert1(CGRectGetWidth(bounds), @"Zero width for view %@", view);
|
||||
NSAssert1(CGRectGetHeight(bounds), @"Zero height for view %@", view);
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, 0);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
NSAssert1(context, @"Could not generate context for layer %@", layer);
|
||||
NSAssert1(context, @"Could not generate context for view %@", view);
|
||||
|
||||
UIGraphicsPushContext(context);
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
[layer renderInContext:context];
|
||||
BOOL success = [view drawViewHierarchyInRect:bounds afterScreenUpdates:YES];
|
||||
NSAssert1(success, @"Could not create snapshot for view %@", view);
|
||||
}
|
||||
CGContextRestoreGState(context);
|
||||
UIGraphicsPopContext();
|
||||
|
@ -388,10 +348,4 @@ typedef NS_ENUM(NSUInteger, FBTestSnapshotFileNameType) {
|
|||
return snapshot;
|
||||
}
|
||||
|
||||
- (UIImage *)_renderView:(UIView *)view
|
||||
{
|
||||
[view layoutIfNeeded];
|
||||
return [self _renderLayer:view.layer];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 73 KiB |
After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 153 KiB |
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 166 KiB |
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 166 KiB |
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 203 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 364 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 365 KiB |
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |