no message
This commit is contained in:
1 parent
de5bd21a43
commit
568ad5c409
262 files changed
+23226
-78
No files matched your search
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// EXPRuntimeMatcher.h
|
||||
// Expecta
|
||||
//
|
||||
// Created by Luke Redpath on 26/03/2012.
|
||||
// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "EXPMatcher.h"
|
||||
#import "EXPDefines.h"
|
||||
|
||||
@interface EXPBlockDefinedMatcher : NSObject <EXPMatcher> {
|
||||
EXPBoolBlock prerequisiteBlock;
|
||||
EXPBoolBlock matchBlock;
|
||||
EXPStringBlock failureMessageForToBlock;
|
||||
EXPStringBlock failureMessageForNotToBlock;
|
||||
}
|
||||
|
||||
@property(nonatomic, copy) EXPBoolBlock prerequisiteBlock;
|
||||
@property(nonatomic, copy) EXPBoolBlock matchBlock;
|
||||
@property(nonatomic, copy) EXPStringBlock failureMessageForToBlock;
|
||||
@property(nonatomic, copy) EXPStringBlock failureMessageForNotToBlock;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// EXPRuntimeMatcher.m
|
||||
// Expecta
|
||||
//
|
||||
// Created by Luke Redpath on 26/03/2012.
|
||||
// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved.
|
||||
//
|
||||
|
||||
#import "EXPBlockDefinedMatcher.h"
|
||||
|
||||
@implementation EXPBlockDefinedMatcher
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
self.prerequisiteBlock = nil;
|
||||
self.matchBlock = nil;
|
||||
self.failureMessageForToBlock = nil;
|
||||
self.failureMessageForNotToBlock = nil;
|
||||
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@synthesize prerequisiteBlock;
|
||||
@synthesize matchBlock;
|
||||
@synthesize failureMessageForToBlock;
|
||||
@synthesize failureMessageForNotToBlock;
|
||||
|
||||
- (BOOL)meetsPrerequesiteFor:(id)actual
|
||||
{
|
||||
if (self.prerequisiteBlock) {
|
||||
return self.prerequisiteBlock();
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)matches:(id)actual
|
||||
{
|
||||
if (self.matchBlock) {
|
||||
return self.matchBlock();
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)failureMessageForTo:(id)actual
|
||||
{
|
||||
if (self.failureMessageForToBlock) {
|
||||
return self.failureMessageForToBlock();
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)failureMessageForNotTo:(id)actual
|
||||
{
|
||||
if (self.failureMessageForNotToBlock) {
|
||||
return self.failureMessageForNotToBlock();
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// EXPDefines.h
|
||||
// Expecta
|
||||
//
|
||||
// Created by Luke Redpath on 26/03/2012.
|
||||
// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Expecta_EXPDefines_h
|
||||
#define Expecta_EXPDefines_h
|
||||
|
||||
typedef void (^EXPBasicBlock)();
|
||||
typedef id (^EXPIdBlock)();
|
||||
typedef BOOL (^EXPBoolBlock)();
|
||||
typedef NSString *(^EXPStringBlock)();
|
||||
|
||||
#endif
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface EXPDoubleTuple : NSObject {
|
||||
double *_values;
|
||||
size_t _size;
|
||||
}
|
||||
|
||||
@property (nonatomic, assign) double *values;
|
||||
@property (nonatomic, assign) size_t size;
|
||||
|
||||
- (instancetype)initWithDoubleValues:(double *)values size:(size_t)size NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#import "EXPDoubleTuple.h"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
@implementation EXPDoubleTuple
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@synthesize values = _values, size = _size;
|
||||
|
||||
- (instancetype)initWithDoubleValues:(double *)values size:(size_t)size {
|
||||
if ((self = [super init])) {
|
||||
self.values = malloc(sizeof(double) * size);
|
||||
memcpy(self.values, values, sizeof(double) * size);
|
||||
self.size = size;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
free(self.values);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
if (![object isKindOfClass:[EXPDoubleTuple class]]) return NO;
|
||||
EXPDoubleTuple *other = (EXPDoubleTuple *)object;
|
||||
if (self.size == other.size) {
|
||||
for (int i = 0; i < self.size; ++i) {
|
||||
if (self.values[i] != other.values[i]) return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
if (self.size == 2) {
|
||||
return [NSString stringWithFormat:@"Double tuple: {%f, %f}", self.values[0], self.values[1]];
|
||||
} else if (self.size == 4) {
|
||||
return [NSString stringWithFormat:@"Double tuple: {%f, %f, %f, %f}", self.values[0], self.values[1], self.values[2], self.values[3]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"Double tuple of unexpected size %zd, sadly", self.size];
|
||||
}
|
||||
|
||||
@end
|
||||
Generated
+45
@@ -0,0 +1,45 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "EXPMatcher.h"
|
||||
#import "EXPDefines.h"
|
||||
|
||||
@interface EXPExpect : NSObject {
|
||||
EXPIdBlock _actualBlock;
|
||||
id _testCase;
|
||||
int _lineNumber;
|
||||
char *_fileName;
|
||||
BOOL _negative;
|
||||
BOOL _asynchronous;
|
||||
NSTimeInterval _timeout;
|
||||
}
|
||||
|
||||
@property(nonatomic, copy) EXPIdBlock actualBlock;
|
||||
@property(nonatomic, readonly) id actual;
|
||||
@property(nonatomic, assign) id testCase;
|
||||
@property(nonatomic) int lineNumber;
|
||||
@property(nonatomic) const char *fileName;
|
||||
@property(nonatomic) BOOL negative;
|
||||
@property(nonatomic) BOOL asynchronous;
|
||||
@property(nonatomic) NSTimeInterval timeout;
|
||||
|
||||
@property(nonatomic, readonly) EXPExpect *to;
|
||||
@property(nonatomic, readonly) EXPExpect *toNot;
|
||||
@property(nonatomic, readonly) EXPExpect *notTo;
|
||||
@property(nonatomic, readonly) EXPExpect *will;
|
||||
@property(nonatomic, readonly) EXPExpect *willNot;
|
||||
@property(nonatomic, readonly) EXPExpect *(^after)(NSTimeInterval timeInterval);
|
||||
|
||||
- (instancetype)initWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName NS_DESIGNATED_INITIALIZER;
|
||||
+ (EXPExpect *)expectWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName;
|
||||
|
||||
- (void)applyMatcher:(id<EXPMatcher>)matcher;
|
||||
- (void)applyMatcher:(id<EXPMatcher>)matcher to:(NSObject **)actual;
|
||||
|
||||
@end
|
||||
|
||||
@interface EXPDynamicPredicateMatcher : NSObject <EXPMatcher> {
|
||||
EXPExpect *_expectation;
|
||||
SEL _selector;
|
||||
}
|
||||
- (instancetype)initWithExpectation:(EXPExpect *)expectation selector:(SEL)selector NS_DESIGNATED_INITIALIZER;
|
||||
@property (nonatomic, readonly, copy) void (^dispatch)(void);
|
||||
@end
|
||||
Generated
+221
@@ -0,0 +1,221 @@
|
||||
#import "EXPExpect.h"
|
||||
#import "NSObject+Expecta.h"
|
||||
#import "Expecta.h"
|
||||
#import "EXPUnsupportedObject.h"
|
||||
#import "EXPMatcher.h"
|
||||
#import "EXPBlockDefinedMatcher.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
@implementation EXPExpect
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@dynamic
|
||||
actual,
|
||||
to,
|
||||
toNot,
|
||||
notTo,
|
||||
will,
|
||||
willNot,
|
||||
after;
|
||||
|
||||
@synthesize
|
||||
actualBlock=_actualBlock,
|
||||
testCase=_testCase,
|
||||
negative=_negative,
|
||||
asynchronous=_asynchronous,
|
||||
timeout=_timeout,
|
||||
lineNumber=_lineNumber,
|
||||
fileName=_fileName;
|
||||
|
||||
- (instancetype)initWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
self.actualBlock = actualBlock;
|
||||
self.testCase = testCase;
|
||||
self.negative = NO;
|
||||
self.asynchronous = NO;
|
||||
self.timeout = [Expecta asynchronousTestTimeout];
|
||||
self.lineNumber = lineNumber;
|
||||
self.fileName = fileName;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[_actualBlock release];
|
||||
_actualBlock = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
+ (EXPExpect *)expectWithActualBlock:(id)actualBlock testCase:(id)testCase lineNumber:(int)lineNumber fileName:(const char *)fileName {
|
||||
return [[[EXPExpect alloc] initWithActualBlock:actualBlock testCase:(id)testCase lineNumber:lineNumber fileName:fileName] autorelease];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (EXPExpect *)to {
|
||||
return self;
|
||||
}
|
||||
|
||||
- (EXPExpect *)toNot {
|
||||
self.negative = !self.negative;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (EXPExpect *)notTo {
|
||||
return [self toNot];
|
||||
}
|
||||
|
||||
- (EXPExpect *)will {
|
||||
self.asynchronous = YES;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (EXPExpect *)willNot {
|
||||
return self.will.toNot;
|
||||
}
|
||||
|
||||
- (EXPExpect *(^)(NSTimeInterval))after
|
||||
{
|
||||
EXPExpect * (^block)(NSTimeInterval) = [^EXPExpect *(NSTimeInterval timeout) {
|
||||
self.asynchronous = YES;
|
||||
self.timeout = timeout;
|
||||
return self;
|
||||
} copy];
|
||||
|
||||
return [block autorelease];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (id)actual {
|
||||
if(self.actualBlock) {
|
||||
return self.actualBlock();
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)applyMatcher:(id<EXPMatcher>)matcher
|
||||
{
|
||||
id actual = [self actual];
|
||||
[self applyMatcher:matcher to:&actual];
|
||||
}
|
||||
|
||||
- (void)applyMatcher:(id<EXPMatcher>)matcher to:(NSObject **)actual {
|
||||
if([*actual isKindOfClass:[EXPUnsupportedObject class]]) {
|
||||
EXPFail(self.testCase, self.lineNumber, self.fileName,
|
||||
[NSString stringWithFormat:@"expecting a %@ is not supported", ((EXPUnsupportedObject *)*actual).type]);
|
||||
} else {
|
||||
BOOL failed = NO;
|
||||
if([matcher respondsToSelector:@selector(meetsPrerequesiteFor:)] &&
|
||||
![matcher meetsPrerequesiteFor:*actual]) {
|
||||
failed = YES;
|
||||
} else {
|
||||
BOOL matchResult = NO;
|
||||
if(self.asynchronous) {
|
||||
NSTimeInterval timeOut = self.timeout;
|
||||
NSDate *expiryDate = [NSDate dateWithTimeIntervalSinceNow:timeOut];
|
||||
while(1) {
|
||||
matchResult = [matcher matches:*actual];
|
||||
failed = self.negative ? matchResult : !matchResult;
|
||||
if(!failed || ([(NSDate *)[NSDate date] compare:expiryDate] == NSOrderedDescending)) {
|
||||
break;
|
||||
}
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
|
||||
OSMemoryBarrier();
|
||||
*actual = self.actual;
|
||||
}
|
||||
} else {
|
||||
matchResult = [matcher matches:*actual];
|
||||
}
|
||||
failed = self.negative ? matchResult : !matchResult;
|
||||
}
|
||||
if(failed) {
|
||||
NSString *message = nil;
|
||||
|
||||
if(self.negative) {
|
||||
if ([matcher respondsToSelector:@selector(failureMessageForNotTo:)]) {
|
||||
message = [matcher failureMessageForNotTo:*actual];
|
||||
}
|
||||
} else {
|
||||
if ([matcher respondsToSelector:@selector(failureMessageForTo:)]) {
|
||||
message = [matcher failureMessageForTo:*actual];
|
||||
}
|
||||
}
|
||||
if (message == nil) {
|
||||
message = @"Match Failed.";
|
||||
}
|
||||
|
||||
EXPFail(self.testCase, self.lineNumber, self.fileName, message);
|
||||
}
|
||||
}
|
||||
self.negative = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Dynamic predicate dispatch
|
||||
|
||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|
||||
{
|
||||
if ([self.actual respondsToSelector:aSelector]) {
|
||||
return [self.actual methodSignatureForSelector:aSelector];
|
||||
}
|
||||
return [super methodSignatureForSelector:aSelector];
|
||||
}
|
||||
|
||||
- (void)forwardInvocation:(NSInvocation *)anInvocation
|
||||
{
|
||||
if ([self.actual respondsToSelector:anInvocation.selector]) {
|
||||
EXPDynamicPredicateMatcher *matcher = [[EXPDynamicPredicateMatcher alloc] initWithExpectation:self selector:anInvocation.selector];
|
||||
[anInvocation setSelector:@selector(dispatch)];
|
||||
[anInvocation invokeWithTarget:matcher];
|
||||
[matcher release];
|
||||
}
|
||||
else {
|
||||
[super forwardInvocation:anInvocation];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
@implementation EXPDynamicPredicateMatcher
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
- (instancetype)initWithExpectation:(EXPExpect *)expectation selector:(SEL)selector
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_expectation = expectation;
|
||||
_selector = selector;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)matches:(id)actual
|
||||
{
|
||||
return (BOOL)[actual performSelector:_selector];
|
||||
}
|
||||
|
||||
- (NSString *)failureMessageForTo:(id)actual
|
||||
{
|
||||
return [NSString stringWithFormat:@"expected %@ to be true", NSStringFromSelector(_selector)];
|
||||
}
|
||||
|
||||
- (NSString *)failureMessageForNotTo:(id)actual
|
||||
{
|
||||
return [NSString stringWithFormat:@"expected %@ to be false", NSStringFromSelector(_selector)];
|
||||
}
|
||||
|
||||
- (void (^)(void))dispatch
|
||||
{
|
||||
__block id blockExpectation = _expectation;
|
||||
|
||||
return [[^{
|
||||
[blockExpectation applyMatcher:self];
|
||||
} copy] autorelease];
|
||||
}
|
||||
|
||||
@end
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface EXPFloatTuple : NSObject {
|
||||
float *_values;
|
||||
size_t _size;
|
||||
}
|
||||
|
||||
@property (nonatomic, assign) float *values;
|
||||
@property (nonatomic, assign) size_t size;
|
||||
|
||||
- (instancetype)initWithFloatValues:(float *)values size:(size_t)size NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
#import "EXPFloatTuple.h"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
@implementation EXPFloatTuple
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@synthesize values = _values, size = _size;
|
||||
|
||||
- (instancetype)initWithFloatValues:(float *)values size:(size_t)size {
|
||||
if ((self = [super init])) {
|
||||
self.values = malloc(sizeof(float) * size);
|
||||
memcpy(self.values, values, sizeof(float) * size);
|
||||
self.size = size;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
free(self.values);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object {
|
||||
if (![object isKindOfClass:[EXPFloatTuple class]]) return NO;
|
||||
EXPFloatTuple *other = (EXPFloatTuple *)object;
|
||||
if (self.size == other.size) {
|
||||
for (int i = 0; i < self.size; ++i) {
|
||||
if (self.values[i] != other.values[i]) return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
NSUInteger prime = 31;
|
||||
NSUInteger hash = 0;
|
||||
for (int i=0; i<self.size; i++) {
|
||||
hash = prime * hash + (NSUInteger)self.values[i];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
if (self.size == 2) {
|
||||
return [NSString stringWithFormat:@"Float tuple: {%f, %f}", self.values[0], self.values[1]];
|
||||
} else if (self.size == 4) {
|
||||
return [NSString stringWithFormat:@"Float tuple: {%f, %f, %f, %f}", self.values[0], self.values[1], self.values[2], self.values[3]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"Float tuple of unexpected size %zd, sadly", self.size];
|
||||
}
|
||||
|
||||
@end
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// EXPMatcher.h
|
||||
// Expecta
|
||||
//
|
||||
// Created by Luke Redpath on 26/03/2012.
|
||||
// Copyright (c) 2012 Peter Jihoon Kim. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol EXPMatcher <NSObject>
|
||||
|
||||
- (BOOL)matches:(id)actual;
|
||||
|
||||
@optional
|
||||
- (BOOL)meetsPrerequesiteFor:(id)actual;
|
||||
- (NSString *)failureMessageForTo:(id)actual;
|
||||
- (NSString *)failureMessageForNotTo:(id)actual;
|
||||
|
||||
@end
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface EXPUnsupportedObject : NSObject {
|
||||
NSString *_type;
|
||||
}
|
||||
|
||||
@property (nonatomic, retain) NSString *type;
|
||||
|
||||
- (instancetype)initWithType:(NSString *)type NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#import "EXPUnsupportedObject.h"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
|
||||
@implementation EXPUnsupportedObject
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@synthesize type=_type;
|
||||
|
||||
- (instancetype)initWithType:(NSString *)type {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
self.type = type;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
self.type = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@end
|
||||
Generated
+15
@@ -0,0 +1,15 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for Expecta.
|
||||
FOUNDATION_EXPORT double ExpectaVersionNumber;
|
||||
|
||||
//! Project version string for Expecta.
|
||||
FOUNDATION_EXPORT const unsigned char ExpectaVersionString[];
|
||||
|
||||
#import <Expecta/ExpectaObject.h>
|
||||
#import <Expecta/ExpectaSupport.h>
|
||||
#import <Expecta/EXPMatchers.h>
|
||||
|
||||
// Enable shorthand by default
|
||||
#define expect(...) EXP_expect((__VA_ARGS__))
|
||||
#define failure(...) EXP_failure((__VA_ARGS__))
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define EXPObjectify(value) _EXPObjectify(@encode(__typeof__((value))), (value))
|
||||
#define EXP_expect(actual) _EXP_expect(self, __LINE__, __FILE__, ^id{ __typeof__((actual)) strongActual = (actual); return EXPObjectify(strongActual); })
|
||||
#define EXPMatcherInterface(matcherName, matcherArguments) _EXPMatcherInterface(matcherName, matcherArguments)
|
||||
#define EXPMatcherImplementationBegin(matcherName, matcherArguments) _EXPMatcherImplementationBegin(matcherName, matcherArguments)
|
||||
#define EXPMatcherImplementationEnd _EXPMatcherImplementationEnd
|
||||
#define EXPMatcherAliasImplementation(newMatcherName, oldMatcherName, matcherArguments) _EXPMatcherAliasImplementation(newMatcherName, oldMatcherName, matcherArguments)
|
||||
|
||||
#define EXP_failure(message) EXPFail(self, __LINE__, __FILE__, message)
|
||||
|
||||
|
||||
@interface Expecta : NSObject
|
||||
|
||||
+ (NSTimeInterval)asynchronousTestTimeout;
|
||||
+ (void)setAsynchronousTestTimeout:(NSTimeInterval)timeout;
|
||||
|
||||
@end
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#import "ExpectaObject.h"
|
||||
|
||||
@implementation Expecta
|
||||
|
||||
static NSTimeInterval _asynchronousTestTimeout = 1.0;
|
||||
|
||||
+ (NSTimeInterval)asynchronousTestTimeout {
|
||||
return _asynchronousTestTimeout;
|
||||
}
|
||||
|
||||
+ (void)setAsynchronousTestTimeout:(NSTimeInterval)timeout {
|
||||
_asynchronousTestTimeout = timeout;
|
||||
}
|
||||
|
||||
@end
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
#import "EXPExpect.h"
|
||||
#import "EXPBlockDefinedMatcher.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
id _EXPObjectify(const char *type, ...);
|
||||
EXPExpect *_EXP_expect(id testCase, int lineNumber, const char *fileName, EXPIdBlock actualBlock);
|
||||
|
||||
void EXPFail(id testCase, int lineNumber, const char *fileName, NSString *message);
|
||||
NSString *EXPDescribeObject(id obj);
|
||||
|
||||
void EXP_prerequisite(EXPBoolBlock block);
|
||||
void EXP_match(EXPBoolBlock block);
|
||||
void EXP_failureMessageForTo(EXPStringBlock block);
|
||||
void EXP_failureMessageForNotTo(EXPStringBlock block);
|
||||
|
||||
#if __has_feature(objc_arc)
|
||||
#define _EXP_release(x)
|
||||
#define _EXP_autorelease(x) (x)
|
||||
|
||||
#else
|
||||
#define _EXP_release(x) [x release]
|
||||
#define _EXP_autorelease(x) [x autorelease]
|
||||
#endif
|
||||
|
||||
// workaround for the categories bug: http://developer.apple.com/library/mac/#qa/qa1490/_index.html
|
||||
#define EXPFixCategoriesBug(name) \
|
||||
__attribute__((constructor)) static void EXPFixCategoriesBug##name() {}
|
||||
|
||||
#define _EXPMatcherInterface(matcherName, matcherArguments) \
|
||||
@interface EXPExpect (matcherName##Matcher) \
|
||||
@property (nonatomic, readonly) void(^ matcherName) matcherArguments; \
|
||||
@end
|
||||
|
||||
#define _EXPMatcherImplementationBegin(matcherName, matcherArguments) \
|
||||
EXPFixCategoriesBug(EXPMatcher##matcherName##Matcher); \
|
||||
@implementation EXPExpect (matcherName##Matcher) \
|
||||
@dynamic matcherName;\
|
||||
- (void(^) matcherArguments) matcherName { \
|
||||
EXPBlockDefinedMatcher *matcher = [[EXPBlockDefinedMatcher alloc] init]; \
|
||||
[[[NSThread currentThread] threadDictionary] setObject:matcher forKey:@"EXP_currentMatcher"]; \
|
||||
__block id actual = self.actual; \
|
||||
__block void (^prerequisite)(EXPBoolBlock block) = ^(EXPBoolBlock block) { EXP_prerequisite(block); }; \
|
||||
__block void (^match)(EXPBoolBlock block) = ^(EXPBoolBlock block) { EXP_match(block); }; \
|
||||
__block void (^failureMessageForTo)(EXPStringBlock block) = ^(EXPStringBlock block) { EXP_failureMessageForTo(block); }; \
|
||||
__block void (^failureMessageForNotTo)(EXPStringBlock block) = ^(EXPStringBlock block) { EXP_failureMessageForNotTo(block); }; \
|
||||
prerequisite(nil); match(nil); failureMessageForTo(nil); failureMessageForNotTo(nil); \
|
||||
void (^matcherBlock) matcherArguments = [^ matcherArguments { \
|
||||
{
|
||||
|
||||
#define _EXPMatcherImplementationEnd \
|
||||
} \
|
||||
[self applyMatcher:matcher to:&actual]; \
|
||||
[[[NSThread currentThread] threadDictionary] removeObjectForKey:@"EXP_currentMatcher"]; \
|
||||
} copy]; \
|
||||
_EXP_release(matcher); \
|
||||
return _EXP_autorelease(matcherBlock); \
|
||||
} \
|
||||
@end
|
||||
|
||||
#define _EXPMatcherAliasImplementation(newMatcherName, oldMatcherName, matcherArguments) \
|
||||
EXPFixCategoriesBug(EXPMatcher##newMatcherName##Matcher); \
|
||||
@implementation EXPExpect (newMatcherName##Matcher) \
|
||||
@dynamic newMatcherName;\
|
||||
- (void(^) matcherArguments) newMatcherName { \
|
||||
return [self oldMatcherName]; \
|
||||
}\
|
||||
@end
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
#import "ExpectaSupport.h"
|
||||
#import "NSValue+Expecta.h"
|
||||
#import "NSObject+Expecta.h"
|
||||
#import "EXPUnsupportedObject.h"
|
||||
#import "EXPFloatTuple.h"
|
||||
#import "EXPDoubleTuple.h"
|
||||
#import "EXPDefines.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
@interface NSObject (ExpectaXCTestRecordFailure)
|
||||
|
||||
// suppress warning
|
||||
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filename atLine:(NSUInteger)lineNumber expected:(BOOL)expected;
|
||||
|
||||
@end
|
||||
|
||||
id _EXPObjectify(const char *type, ...) {
|
||||
va_list v;
|
||||
va_start(v, type);
|
||||
id obj = nil;
|
||||
if(strcmp(type, @encode(char)) == 0) {
|
||||
char actual = (char)va_arg(v, int);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(_Bool)) == 0) {
|
||||
_Static_assert(sizeof(_Bool) <= sizeof(int), "Expected _Bool to be subject to vararg type promotion");
|
||||
_Bool actual = (_Bool)va_arg(v, int);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(double)) == 0) {
|
||||
double actual = (double)va_arg(v, double);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(float)) == 0) {
|
||||
float actual = (float)va_arg(v, double);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(int)) == 0) {
|
||||
int actual = (int)va_arg(v, int);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(long)) == 0) {
|
||||
long actual = (long)va_arg(v, long);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(long long)) == 0) {
|
||||
long long actual = (long long)va_arg(v, long long);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(short)) == 0) {
|
||||
short actual = (short)va_arg(v, int);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(unsigned char)) == 0) {
|
||||
unsigned char actual = (unsigned char)va_arg(v, unsigned int);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(unsigned int)) == 0) {
|
||||
unsigned int actual = (int)va_arg(v, unsigned int);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(unsigned long)) == 0) {
|
||||
unsigned long actual = (unsigned long)va_arg(v, unsigned long);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(unsigned long long)) == 0) {
|
||||
unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
|
||||
obj = @(actual);
|
||||
} else if(strcmp(type, @encode(unsigned short)) == 0) {
|
||||
unsigned short actual = (unsigned short)va_arg(v, unsigned int);
|
||||
obj = @(actual);
|
||||
} else if(strstr(type, @encode(EXPBasicBlock)) != NULL) {
|
||||
// @encode(EXPBasicBlock) returns @? as of clang 4.1.
|
||||
// This condition must occur before the test for id/class type,
|
||||
// otherwise blocks will be treated as vanilla objects.
|
||||
id actual = va_arg(v, EXPBasicBlock);
|
||||
obj = [[actual copy] autorelease];
|
||||
} else if((strstr(type, @encode(id)) != NULL) || (strstr(type, @encode(Class)) != 0)) {
|
||||
id actual = va_arg(v, id);
|
||||
obj = actual;
|
||||
} else if(strcmp(type, @encode(__typeof__(nil))) == 0) {
|
||||
obj = nil;
|
||||
} else if(strstr(type, "ff}{") != NULL) { //TODO: of course this only works for a 2x2 e.g. CGRect
|
||||
obj = [[[EXPFloatTuple alloc] initWithFloatValues:(float *)va_arg(v, float[4]) size:4] autorelease];
|
||||
} else if(strstr(type, "=ff}") != NULL) {
|
||||
obj = [[[EXPFloatTuple alloc] initWithFloatValues:(float *)va_arg(v, float[2]) size:2] autorelease];
|
||||
} else if(strstr(type, "=ffff}") != NULL) {
|
||||
obj = [[[EXPFloatTuple alloc] initWithFloatValues:(float *)va_arg(v, float[4]) size:4] autorelease];
|
||||
} else if(strstr(type, "dd}{") != NULL) { //TODO: same here
|
||||
obj = [[[EXPDoubleTuple alloc] initWithDoubleValues:(double *)va_arg(v, double[4]) size:4] autorelease];
|
||||
} else if(strstr(type, "=dd}") != NULL) {
|
||||
obj = [[[EXPDoubleTuple alloc] initWithDoubleValues:(double *)va_arg(v, double[2]) size:2] autorelease];
|
||||
} else if(strstr(type, "=dddd}") != NULL) {
|
||||
obj = [[[EXPDoubleTuple alloc] initWithDoubleValues:(double *)va_arg(v, double[4]) size:4] autorelease];
|
||||
} else if(type[0] == '{') {
|
||||
EXPUnsupportedObject *actual = [[[EXPUnsupportedObject alloc] initWithType:@"struct"] autorelease];
|
||||
obj = actual;
|
||||
} else if(type[0] == '(') {
|
||||
EXPUnsupportedObject *actual = [[[EXPUnsupportedObject alloc] initWithType:@"union"] autorelease];
|
||||
obj = actual;
|
||||
} else {
|
||||
void *actual = va_arg(v, void *);
|
||||
obj = (actual == NULL ? nil :[NSValue valueWithPointer:actual]);
|
||||
}
|
||||
if([obj isKindOfClass:[NSValue class]] && ![obj isKindOfClass:[NSNumber class]]) {
|
||||
[(NSValue *)obj set_EXP_objCType:type];
|
||||
}
|
||||
va_end(v);
|
||||
return obj;
|
||||
}
|
||||
|
||||
EXPExpect *_EXP_expect(id testCase, int lineNumber, const char *fileName, EXPIdBlock actualBlock) {
|
||||
return [EXPExpect expectWithActualBlock:actualBlock testCase:testCase lineNumber:lineNumber fileName:fileName];
|
||||
}
|
||||
|
||||
void EXPFail(id testCase, int lineNumber, const char *fileName, NSString *message) {
|
||||
NSLog(@"%s:%d %@", fileName, lineNumber, message);
|
||||
NSString *reason = [NSString stringWithFormat:@"%s:%d %@", fileName, lineNumber, message];
|
||||
NSException *exception = [NSException exceptionWithName:@"Expecta Error" reason:reason userInfo:nil];
|
||||
|
||||
if(testCase && [testCase respondsToSelector:@selector(recordFailureWithDescription:inFile:atLine:expected:)]){
|
||||
[testCase recordFailureWithDescription:message
|
||||
inFile:@(fileName)
|
||||
atLine:lineNumber
|
||||
expected:NO];
|
||||
} else {
|
||||
[exception raise];
|
||||
}
|
||||
}
|
||||
|
||||
NSString *EXPDescribeObject(id obj) {
|
||||
if(obj == nil) {
|
||||
return @"nil/null";
|
||||
} else if([obj isKindOfClass:[NSValue class]] && ![obj isKindOfClass:[NSNumber class]]) {
|
||||
const char *type = [(NSValue *)obj _EXP_objCType];
|
||||
if(type) {
|
||||
if(strcmp(type, @encode(SEL)) == 0) {
|
||||
return [NSString stringWithFormat:@"@selector(%@)", NSStringFromSelector([obj pointerValue])];
|
||||
} else if(strcmp(type, @encode(Class)) == 0) {
|
||||
return NSStringFromClass([obj pointerValue]);
|
||||
}
|
||||
}
|
||||
}
|
||||
NSString *description = [obj description];
|
||||
if([obj isKindOfClass:[NSArray class]]) {
|
||||
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[obj count]];
|
||||
for(id o in obj) {
|
||||
[arr addObject:EXPDescribeObject(o)];
|
||||
}
|
||||
description = [NSString stringWithFormat:@"(%@)", [arr componentsJoinedByString:@", "]];
|
||||
} else if([obj isKindOfClass:[NSSet class]] || [obj isKindOfClass:[NSOrderedSet class]]) {
|
||||
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[obj count]];
|
||||
for(id o in obj) {
|
||||
[arr addObject:EXPDescribeObject(o)];
|
||||
}
|
||||
description = [NSString stringWithFormat:@"{(%@)}", [arr componentsJoinedByString:@", "]];
|
||||
} else if([obj isKindOfClass:[NSDictionary class]]) {
|
||||
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[obj count]];
|
||||
for(id k in obj) {
|
||||
id v = obj[k];
|
||||
[arr addObject:[NSString stringWithFormat:@"%@ = %@;",EXPDescribeObject(k), EXPDescribeObject(v)]];
|
||||
}
|
||||
description = [NSString stringWithFormat:@"{%@}", [arr componentsJoinedByString:@" "]];
|
||||
} else if([obj isKindOfClass:[NSAttributedString class]]) {
|
||||
description = [obj string];
|
||||
} else {
|
||||
description = [description stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
void EXP_prerequisite(EXPBoolBlock block) {
|
||||
[[[NSThread currentThread] threadDictionary][@"EXP_currentMatcher"] setPrerequisiteBlock:block];
|
||||
}
|
||||
|
||||
void EXP_match(EXPBoolBlock block) {
|
||||
[[[NSThread currentThread] threadDictionary][@"EXP_currentMatcher"] setMatchBlock:block];
|
||||
}
|
||||
|
||||
void EXP_failureMessageForTo(EXPStringBlock block) {
|
||||
[[[NSThread currentThread] threadDictionary][@"EXP_currentMatcher"] setFailureMessageForToBlock:block];
|
||||
}
|
||||
|
||||
void EXP_failureMessageForNotTo(EXPStringBlock block) {
|
||||
[[[NSThread currentThread] threadDictionary][@"EXP_currentMatcher"] setFailureMessageForNotToBlock:block];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
BOOL EXPIsValuePointer(NSValue *value);
|
||||
BOOL EXPIsNumberFloat(NSNumber *number);
|
||||
@@ -0,0 +1,9 @@
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
BOOL EXPIsValuePointer(NSValue *value) {
|
||||
return [value objCType][0] == @encode(void *)[0];
|
||||
}
|
||||
|
||||
BOOL EXPIsNumberFloat(NSNumber *number) {
|
||||
return strcmp([number objCType], @encode(float)) == 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(_beCloseToWithin, (id expected, id within));
|
||||
EXPMatcherInterface(beCloseToWithin, (id expected, id within));
|
||||
|
||||
#define beCloseTo(expected) _beCloseToWithin(EXPObjectify((expected)), nil)
|
||||
#define beCloseToWithin(expected, range) _beCloseToWithin(EXPObjectify((expected)), EXPObjectify((range)))
|
||||
@@ -0,0 +1,49 @@
|
||||
#import "EXPMatchers+beCloseTo.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(_beCloseToWithin, (id expected, id within)) {
|
||||
prerequisite(^BOOL{
|
||||
return [actual isKindOfClass:[NSNumber class]] &&
|
||||
[expected isKindOfClass:[NSNumber class]] &&
|
||||
([within isKindOfClass:[NSNumber class]] || (within == nil));
|
||||
});
|
||||
|
||||
match(^BOOL{
|
||||
double actualValue = [actual doubleValue];
|
||||
double expectedValue = [expected doubleValue];
|
||||
|
||||
if (within != nil) {
|
||||
double withinValue = [within doubleValue];
|
||||
double lowerBound = expectedValue - withinValue;
|
||||
double upperBound = expectedValue + withinValue;
|
||||
return (actualValue >= lowerBound) && (actualValue <= upperBound);
|
||||
} else {
|
||||
double diff = fabs(actualValue - expectedValue);
|
||||
actualValue = fabs(actualValue);
|
||||
expectedValue = fabs(expectedValue);
|
||||
double largest = (expectedValue > actualValue) ? expectedValue : actualValue;
|
||||
return (diff <= largest * FLT_EPSILON);
|
||||
}
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if (within) {
|
||||
return [NSString stringWithFormat:@"expected %@ to be close to %@ within %@",
|
||||
EXPDescribeObject(actual), EXPDescribeObject(expected), EXPDescribeObject(within)];
|
||||
} else {
|
||||
return [NSString stringWithFormat:@"expected %@ to be close to %@",
|
||||
EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
}
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if (within) {
|
||||
return [NSString stringWithFormat:@"expected %@ not to be close to %@ within %@",
|
||||
EXPDescribeObject(actual), EXPDescribeObject(expected), EXPDescribeObject(within)];
|
||||
} else {
|
||||
return [NSString stringWithFormat:@"expected %@ not to be close to %@",
|
||||
EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
}
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,3 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(beFalsy, (void));
|
||||
@@ -0,0 +1,24 @@
|
||||
#import "EXPMatchers+beFalsy.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(beFalsy, (void)) {
|
||||
match(^BOOL{
|
||||
if([actual isKindOfClass:[NSNumber class]]) {
|
||||
return ![(NSNumber *)actual boolValue];
|
||||
} else if([actual isKindOfClass:[NSValue class]]) {
|
||||
if(EXPIsValuePointer((NSValue *)actual)) {
|
||||
return ![(NSValue *)actual pointerValue];
|
||||
}
|
||||
}
|
||||
return !actual;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: a falsy value, got: %@, which is truthy", EXPDescribeObject(actual)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: a non-falsy value, got: %@, which is falsy", EXPDescribeObject(actual)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,6 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(_beGreaterThan, (id expected));
|
||||
EXPMatcherInterface(beGreaterThan, (id expected));
|
||||
|
||||
#define beGreaterThan(expected) _beGreaterThan(EXPObjectify((expected)))
|
||||
@@ -0,0 +1,20 @@
|
||||
#import "EXPMatchers+beGreaterThan.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(_beGreaterThan, (id expected)) {
|
||||
match(^BOOL{
|
||||
if ([actual respondsToSelector:@selector(compare:)]) {
|
||||
return [actual compare:expected] == NSOrderedDescending;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ to be greater than %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ not to be greater than %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,6 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(_beGreaterThanOrEqualTo, (id expected));
|
||||
EXPMatcherInterface(beGreaterThanOrEqualTo, (id expected));
|
||||
|
||||
#define beGreaterThanOrEqualTo(expected) _beGreaterThanOrEqualTo(EXPObjectify((expected)))
|
||||
@@ -0,0 +1,20 @@
|
||||
#import "EXPMatchers+beGreaterThanOrEqualTo.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(_beGreaterThanOrEqualTo, (id expected)) {
|
||||
match(^BOOL{
|
||||
if ([actual respondsToSelector:@selector(compare:)]) {
|
||||
return [actual compare:expected] != NSOrderedAscending;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ to be greater than or equal to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ not to be greater than or equal to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,10 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(_beIdenticalTo, (void *expected));
|
||||
EXPMatcherInterface(beIdenticalTo, (void *expected)); // to aid code completion
|
||||
|
||||
#if __has_feature(objc_arc)
|
||||
#define beIdenticalTo(expected) _beIdenticalTo((__bridge void*)expected)
|
||||
#else
|
||||
#define beIdenticalTo(expected) _beIdenticalTo(expected)
|
||||
#endif
|
||||
@@ -0,0 +1,24 @@
|
||||
#import "EXPMatchers+equal.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(_beIdenticalTo, (void *expected)) {
|
||||
match(^BOOL{
|
||||
if(actual == expected) {
|
||||
return YES;
|
||||
} else if([actual isKindOfClass:[NSValue class]] && EXPIsValuePointer((NSValue *)actual)) {
|
||||
if([(NSValue *)actual pointerValue] == expected) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: <%p>, got: <%p>", expected, actual];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: not <%p>, got: <%p>", expected, actual];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,6 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(_beInTheRangeOf, (id expectedLowerBound, id expectedUpperBound));
|
||||
EXPMatcherInterface(beInTheRangeOf, (id expectedLowerBound, id expectedUpperBound));
|
||||
|
||||
#define beInTheRangeOf(expectedLowerBound, expectedUpperBound) _beInTheRangeOf(EXPObjectify((expectedLowerBound)), EXPObjectify((expectedUpperBound)))
|
||||
@@ -0,0 +1,30 @@
|
||||
#import "EXPMatchers+beInTheRangeOf.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(_beInTheRangeOf, (id expectedLowerBound, id expectedUpperBound)) {
|
||||
match(^BOOL{
|
||||
if ([actual respondsToSelector:@selector(compare:)]) {
|
||||
NSComparisonResult compareLowerBound = [expectedLowerBound compare: actual];
|
||||
NSComparisonResult compareUpperBound = [expectedUpperBound compare: actual];
|
||||
if (compareLowerBound == NSOrderedSame) {
|
||||
return YES;
|
||||
}
|
||||
if (compareUpperBound == NSOrderedSame) {
|
||||
return YES;
|
||||
}
|
||||
if ((compareLowerBound == NSOrderedAscending) && (compareUpperBound == NSOrderedDescending)) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ to be in the range [%@, %@] (inclusive)", EXPDescribeObject(actual), EXPDescribeObject(expectedLowerBound), EXPDescribeObject(expectedUpperBound)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ not to be in the range [%@, %@] (inclusive)", EXPDescribeObject(actual), EXPDescribeObject(expectedLowerBound), EXPDescribeObject(expectedUpperBound)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,6 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(beInstanceOf, (Class expected));
|
||||
EXPMatcherInterface(beAnInstanceOf, (Class expected));
|
||||
EXPMatcherInterface(beMemberOf, (Class expected));
|
||||
EXPMatcherInterface(beAMemberOf, (Class expected));
|
||||
@@ -0,0 +1,31 @@
|
||||
#import "EXPMatchers+beInstanceOf.h"
|
||||
|
||||
EXPMatcherImplementationBegin(beInstanceOf, (Class expected)) {
|
||||
BOOL actualIsNil = (actual == nil);
|
||||
BOOL expectedIsNil = (expected == nil);
|
||||
|
||||
prerequisite(^BOOL{
|
||||
return !(actualIsNil || expectedIsNil);
|
||||
});
|
||||
|
||||
match(^BOOL{
|
||||
return [actual isMemberOfClass:expected];
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if(actualIsNil) return @"the actual value is nil/null";
|
||||
if(expectedIsNil) return @"the expected value is nil/null";
|
||||
return [NSString stringWithFormat:@"expected: an instance of %@, got: an instance of %@", [expected class], [actual class]];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if(actualIsNil) return @"the actual value is nil/null";
|
||||
if(expectedIsNil) return @"the expected value is nil/null";
|
||||
return [NSString stringWithFormat:@"expected: not an instance of %@, got: an instance of %@", [expected class], [actual class]];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
|
||||
EXPMatcherAliasImplementation(beAnInstanceOf, beInstanceOf, (Class expected));
|
||||
EXPMatcherAliasImplementation(beMemberOf, beInstanceOf, (Class expected));
|
||||
EXPMatcherAliasImplementation(beAMemberOf, beInstanceOf, (Class expected));
|
||||
@@ -0,0 +1,4 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(beKindOf, (Class expected));
|
||||
EXPMatcherInterface(beAKindOf, (Class expected));
|
||||
@@ -0,0 +1,29 @@
|
||||
#import "EXPMatchers+beKindOf.h"
|
||||
|
||||
EXPMatcherImplementationBegin(beKindOf, (Class expected)) {
|
||||
BOOL actualIsNil = (actual == nil);
|
||||
BOOL expectedIsNil = (expected == nil);
|
||||
|
||||
prerequisite(^BOOL{
|
||||
return !(actualIsNil || expectedIsNil);
|
||||
});
|
||||
|
||||
match(^BOOL{
|
||||
return [actual isKindOfClass:expected];
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if(actualIsNil) return @"the actual value is nil/null";
|
||||
if(expectedIsNil) return @"the expected value is nil/null";
|
||||
return [NSString stringWithFormat:@"expected: a kind of %@, got: an instance of %@, which is not a kind of %@", [expected class], [actual class], [expected class]];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if(actualIsNil) return @"the actual value is nil/null";
|
||||
if(expectedIsNil) return @"the expected value is nil/null";
|
||||
return [NSString stringWithFormat:@"expected: not a kind of %@, got: an instance of %@, which is a kind of %@", [expected class], [actual class], [expected class]];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
|
||||
EXPMatcherAliasImplementation(beAKindOf, beKindOf, (Class expected));
|
||||
@@ -0,0 +1,6 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(_beLessThan, (id expected));
|
||||
EXPMatcherInterface(beLessThan, (id expected));
|
||||
|
||||
#define beLessThan(expected) _beLessThan(EXPObjectify((expected)))
|
||||
@@ -0,0 +1,20 @@
|
||||
#import "EXPMatchers+beLessThan.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(_beLessThan, (id expected)) {
|
||||
match(^BOOL{
|
||||
if ([actual respondsToSelector:@selector(compare:)]) {
|
||||
return [actual compare:expected] == NSOrderedAscending;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ to be less than %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ not to be less than %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,6 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(_beLessThanOrEqualTo, (id expected));
|
||||
EXPMatcherInterface(beLessThanOrEqualTo, (id expected));
|
||||
|
||||
#define beLessThanOrEqualTo(expected) _beLessThanOrEqualTo(EXPObjectify((expected)))
|
||||
@@ -0,0 +1,20 @@
|
||||
#import "EXPMatchers+beLessThanOrEqualTo.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(_beLessThanOrEqualTo, (id expected)) {
|
||||
match(^BOOL{
|
||||
if ([actual respondsToSelector:@selector(compare:)]) {
|
||||
return [actual compare:expected] != NSOrderedDescending;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ to be less than or equal to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ not to be less than or equal to %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,4 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(beNil, (void));
|
||||
EXPMatcherInterface(beNull, (void));
|
||||
@@ -0,0 +1,18 @@
|
||||
#import "EXPMatchers+beNil.h"
|
||||
|
||||
EXPMatcherImplementationBegin(beNil, (void)) {
|
||||
match(^BOOL{
|
||||
return actual == nil;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: nil/null, got: %@", EXPDescribeObject(actual)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: not nil/null, got: %@", EXPDescribeObject(actual)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
|
||||
EXPMatcherAliasImplementation(beNull, beNil, (void));
|
||||
@@ -0,0 +1,4 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(beSubclassOf, (Class expected));
|
||||
EXPMatcherInterface(beASubclassOf, (Class expected));
|
||||
@@ -0,0 +1,29 @@
|
||||
#import "EXPMatchers+beSubclassOf.h"
|
||||
#import "NSValue+Expecta.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
EXPMatcherImplementationBegin(beSubclassOf, (Class expected)) {
|
||||
__block BOOL actualIsClass = YES;
|
||||
|
||||
prerequisite(^BOOL {
|
||||
actualIsClass = class_isMetaClass(object_getClass(actual));
|
||||
return actualIsClass;
|
||||
});
|
||||
|
||||
match(^BOOL{
|
||||
return [actual isSubclassOfClass:expected];
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if(!actualIsClass) return @"the actual value is not a Class";
|
||||
return [NSString stringWithFormat:@"expected: a subclass of %@, got: a class %@, which is not a subclass of %@", [expected class], actual, [expected class]];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if(!actualIsClass) return @"the actual value is not a Class";
|
||||
return [NSString stringWithFormat:@"expected: not a subclass of %@, got: a class %@, which is a subclass of %@", [expected class], actual, [expected class]];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
|
||||
EXPMatcherAliasImplementation(beASubclassOf, beSubclassOf, (Class expected));
|
||||
@@ -0,0 +1,4 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(beSupersetOf, (id subset));
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#import "EXPMatchers+contain.h"
|
||||
|
||||
EXPMatcherImplementationBegin(beSupersetOf, (id subset)) {
|
||||
BOOL actualIsCompatible = [actual isKindOfClass:[NSDictionary class]] || [actual respondsToSelector:@selector(containsObject:)];
|
||||
BOOL subsetIsNil = (subset == nil);
|
||||
|
||||
// For some instances the isKindOfClass: method returns false, even though
|
||||
// they are both actually dictionaries. e.g. Comparing a NSCFDictionary and a
|
||||
// NSDictionary.
|
||||
// Or in cases when you compare NSMutableArray (which implementation is __NSArrayM:NSMutableArray:NSArray)
|
||||
// and NSArray (which implementation is __NSArrayI:NSArray)
|
||||
BOOL bothAreIdenticalCollectionClasses = ([actual isKindOfClass:[NSDictionary class]] && [subset isKindOfClass:[NSDictionary class]]) ||
|
||||
([actual isKindOfClass:[NSArray class]] && [subset isKindOfClass:[NSArray class]]) ||
|
||||
([actual isKindOfClass:[NSSet class]] && [subset isKindOfClass:[NSSet class]]) ||
|
||||
([actual isKindOfClass:[NSOrderedSet class]] && [subset isKindOfClass:[NSOrderedSet class]]);
|
||||
|
||||
BOOL classMatches = bothAreIdenticalCollectionClasses || [subset isKindOfClass:[actual class]];
|
||||
|
||||
prerequisite(^BOOL{
|
||||
return actualIsCompatible && !subsetIsNil && classMatches;
|
||||
});
|
||||
|
||||
match(^BOOL{
|
||||
if(!actualIsCompatible) return NO;
|
||||
|
||||
if([actual isKindOfClass:[NSDictionary class]]) {
|
||||
for (id key in subset) {
|
||||
id actualValue = [actual valueForKey:key];
|
||||
id subsetValue = [subset valueForKey:key];
|
||||
|
||||
if (![subsetValue isEqual:actualValue]) return NO;
|
||||
}
|
||||
} else {
|
||||
for (id object in subset) {
|
||||
if (![actual containsObject:object]) return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSDictionary and does not implement -containsObject:", EXPDescribeObject(actual)];
|
||||
|
||||
if(subsetIsNil) return @"the expected value is nil/null";
|
||||
|
||||
if(!classMatches) return [NSString stringWithFormat:@"%@ does not match the class of %@", EXPDescribeObject(subset), EXPDescribeObject(actual)];
|
||||
|
||||
return [NSString stringWithFormat:@"expected %@ to be a superset of %@", EXPDescribeObject(actual), EXPDescribeObject(subset)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSDictionary and does not implement -containsObject:", EXPDescribeObject(actual)];
|
||||
|
||||
if(subsetIsNil) return @"the expected value is nil/null";
|
||||
|
||||
if(!classMatches) return [NSString stringWithFormat:@"%@ does not match the class of %@", EXPDescribeObject(subset), EXPDescribeObject(actual)];
|
||||
|
||||
return [NSString stringWithFormat:@"expected %@ not to be a superset of %@", EXPDescribeObject(actual), EXPDescribeObject(subset)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,3 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(beTruthy, (void));
|
||||
@@ -0,0 +1,24 @@
|
||||
#import "EXPMatchers+beTruthy.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(beTruthy, (void)) {
|
||||
match(^BOOL{
|
||||
if([actual isKindOfClass:[NSNumber class]]) {
|
||||
return !![(NSNumber *)actual boolValue];
|
||||
} else if([actual isKindOfClass:[NSValue class]]) {
|
||||
if(EXPIsValuePointer((NSValue *)actual)) {
|
||||
return !![(NSValue *)actual pointerValue];
|
||||
}
|
||||
}
|
||||
return !!actual;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: a truthy value, got: %@, which is falsy", EXPDescribeObject(actual)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: a non-truthy value, got: %@, which is truthy", EXPDescribeObject(actual)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,4 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(beginWith, (id expected));
|
||||
EXPMatcherInterface(startWith, (id expected));
|
||||
@@ -0,0 +1,51 @@
|
||||
#import "EXPMatchers+beginWith.h"
|
||||
|
||||
EXPMatcherImplementationBegin(beginWith, (id expected)) {
|
||||
BOOL actualIsNil = (actual == nil);
|
||||
BOOL expectedIsNil = (expected == nil);
|
||||
//This condition allows the comparison of an immutable string or ordered collection to the mutable type of the same
|
||||
BOOL actualAndExpectedAreCompatible = (([actual isKindOfClass:[NSString class]] && [expected isKindOfClass:[NSString class]])
|
||||
|| ([actual isKindOfClass:[NSArray class]] && [expected isKindOfClass:[NSArray class]])
|
||||
|| ([actual isKindOfClass:[NSOrderedSet class]] && [expected isKindOfClass:[NSOrderedSet class]]));
|
||||
|
||||
prerequisite(^BOOL {
|
||||
return actualAndExpectedAreCompatible;
|
||||
});
|
||||
|
||||
match(^BOOL {
|
||||
if ([actual isKindOfClass:[NSString class]]) {
|
||||
return [actual hasPrefix:expected];
|
||||
} else if ([actual isKindOfClass:[NSArray class]]) {
|
||||
if ([expected count] > [actual count] || [expected count] == 0) {
|
||||
return NO;
|
||||
}
|
||||
NSArray *subArray = [actual subarrayWithRange:NSMakeRange(0, [expected count])];
|
||||
return [subArray isEqualToArray:expected];
|
||||
} else {
|
||||
if ([expected count] > [actual count] || [expected count] == 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSOrderedSet *subset = [NSOrderedSet orderedSetWithOrderedSet:actual range:NSMakeRange(0, [expected count]) copyItems:NO];
|
||||
return [subset isEqualToOrderedSet:expected];
|
||||
}
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if (actualIsNil) return @"the object is nil/null";
|
||||
if (expectedIsNil) return @"the expected value is nil/null";
|
||||
if (!actualAndExpectedAreCompatible) return [NSString stringWithFormat:@"%@ and %@ are not instances of one of %@, %@, or %@", EXPDescribeObject(actual), EXPDescribeObject(expected), [NSString class], [NSArray class], [NSOrderedSet class]];
|
||||
return [NSString stringWithFormat:@"expected: %@ to begin with %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if (actualIsNil) return @"the object is nil/null";
|
||||
if (expectedIsNil) return @"the expected value is nil/null";
|
||||
if (!actualAndExpectedAreCompatible) return [NSString stringWithFormat:@"%@ and %@ are not instances of one of %@, %@, or %@", EXPDescribeObject(actual), EXPDescribeObject(expected), [NSString class], [NSArray class], [NSOrderedSet class]];
|
||||
|
||||
return [NSString stringWithFormat:@"expected: %@ not to begin with %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
|
||||
EXPMatcherAliasImplementation(startWith, beginWith, (id expected));
|
||||
@@ -0,0 +1,3 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(conformTo, (Protocol *expected));
|
||||
@@ -0,0 +1,33 @@
|
||||
#import "EXPMatchers+conformTo.h"
|
||||
#import "NSValue+Expecta.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
EXPMatcherImplementationBegin(conformTo, (Protocol *expected)) {
|
||||
BOOL actualIsNil = (actual == nil);
|
||||
BOOL expectedIsNil = (expected == nil);
|
||||
|
||||
prerequisite(^BOOL{
|
||||
return !(actualIsNil || expectedIsNil);
|
||||
});
|
||||
|
||||
match(^BOOL{
|
||||
return [actual conformsToProtocol:expected];
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if(actualIsNil) return @"the object is nil/null";
|
||||
if(expectedIsNil) return @"the protocol is nil/null";
|
||||
|
||||
NSString *name = NSStringFromProtocol(expected);
|
||||
return [NSString stringWithFormat:@"expected: %@ to conform to %@", actual, name];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if(actualIsNil) return @"the object is nil/null";
|
||||
if(expectedIsNil) return @"the protocol is nil/null";
|
||||
|
||||
NSString *name = NSStringFromProtocol(expected);
|
||||
return [NSString stringWithFormat:@"expected: %@ not to conform to %@", actual, name];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,5 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(_contain, (id expected));
|
||||
EXPMatcherInterface(contain, (id expected)); // to aid code completion
|
||||
#define contain(expected) _contain(EXPObjectify((expected)))
|
||||
@@ -0,0 +1,38 @@
|
||||
#import "EXPMatchers+contain.h"
|
||||
|
||||
EXPMatcherImplementationBegin(_contain, (id expected)) {
|
||||
BOOL actualIsCompatible = [actual isKindOfClass:[NSString class]] || [actual conformsToProtocol:@protocol(NSFastEnumeration)];
|
||||
BOOL expectedIsNil = (expected == nil);
|
||||
|
||||
prerequisite(^BOOL{
|
||||
return actualIsCompatible && !expectedIsNil;
|
||||
});
|
||||
|
||||
match(^BOOL{
|
||||
if(actualIsCompatible) {
|
||||
if([actual isKindOfClass:[NSString class]]) {
|
||||
return [(NSString *)actual rangeOfString:[expected description]].location != NSNotFound;
|
||||
} else {
|
||||
for (id object in actual) {
|
||||
if ([object isEqual:expected]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSString or NSFastEnumeration", EXPDescribeObject(actual)];
|
||||
if(expectedIsNil) return @"the expected value is nil/null";
|
||||
return [NSString stringWithFormat:@"expected %@ to contain %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSString or NSFastEnumeration", EXPDescribeObject(actual)];
|
||||
if(expectedIsNil) return @"the expected value is nil/null";
|
||||
return [NSString stringWithFormat:@"expected %@ not to contain %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,3 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(endWith, (id expected));
|
||||
@@ -0,0 +1,49 @@
|
||||
#import "EXPMatchers+endWith.h"
|
||||
|
||||
EXPMatcherImplementationBegin(endWith, (id expected)) {
|
||||
BOOL actualIsNil = (actual == nil);
|
||||
BOOL expectedIsNil = (expected == nil);
|
||||
//This condition allows the comparison of an immutable string or ordered collection to the mutable type of the same
|
||||
BOOL actualAndExpectedAreCompatible = (([actual isKindOfClass:[NSString class]] && [expected isKindOfClass:[NSString class]])
|
||||
|| ([actual isKindOfClass:[NSArray class]] && [expected isKindOfClass:[NSArray class]])
|
||||
|| ([actual isKindOfClass:[NSOrderedSet class]] && [expected isKindOfClass:[NSOrderedSet class]]));
|
||||
|
||||
prerequisite(^BOOL {
|
||||
return actualAndExpectedAreCompatible;
|
||||
});
|
||||
|
||||
match(^BOOL {
|
||||
if ([actual isKindOfClass:[NSString class]]) {
|
||||
return [actual hasSuffix:expected];
|
||||
} else if ([actual isKindOfClass:[NSArray class]]) {
|
||||
if ([expected count] > [actual count] || [expected count] == 0) {
|
||||
return NO;
|
||||
}
|
||||
NSArray *subArray = [actual subarrayWithRange:NSMakeRange([actual count] - [expected count], [expected count])];
|
||||
return [subArray isEqualToArray:expected];
|
||||
} else {
|
||||
if ([expected count] > [actual count] || [expected count] == 0) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSOrderedSet *subset = [NSOrderedSet orderedSetWithOrderedSet:actual range:NSMakeRange([actual count] - [expected count], [expected count]) copyItems:NO];
|
||||
return [subset isEqualToOrderedSet:expected];
|
||||
}
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if (actualIsNil) return @"the object is nil/null";
|
||||
if (expectedIsNil) return @"the expected value is nil/null";
|
||||
if (!actualAndExpectedAreCompatible) return [NSString stringWithFormat:@"%@ and %@ are not instances of one of %@, %@, or %@", EXPDescribeObject(actual), EXPDescribeObject(expected), [NSString class], [NSArray class], [NSOrderedSet class]];
|
||||
return [NSString stringWithFormat:@"expected: %@ to end with %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if (actualIsNil) return @"the object is nil/null";
|
||||
if (expectedIsNil) return @"the expected value is nil/null";
|
||||
if (!actualAndExpectedAreCompatible) return [NSString stringWithFormat:@"%@ and %@ are not instances of one of %@, %@, or %@", EXPDescribeObject(actual), EXPDescribeObject(expected), [NSString class], [NSArray class], [NSOrderedSet class]];
|
||||
|
||||
return [NSString stringWithFormat:@"expected: %@ not to end with %@", EXPDescribeObject(actual), EXPDescribeObject(expected)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,5 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(_equal, (id expected));
|
||||
EXPMatcherInterface(equal, (id expected)); // to aid code completion
|
||||
#define equal(...) _equal(EXPObjectify((__VA_ARGS__)))
|
||||
@@ -0,0 +1,38 @@
|
||||
#import "EXPMatchers+equal.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(_equal, (id expected)) {
|
||||
match(^BOOL{
|
||||
if((actual == expected) || [actual isEqual:expected]) {
|
||||
return YES;
|
||||
} else if([actual isKindOfClass:[NSNumber class]] && [expected isKindOfClass:[NSNumber class]]) {
|
||||
if([actual isKindOfClass:[NSDecimalNumber class]] || [expected isKindOfClass:[NSDecimalNumber class]]) {
|
||||
NSDecimalNumber *actualDecimalNumber = [NSDecimalNumber decimalNumberWithDecimal:[(NSNumber *) actual decimalValue]];
|
||||
NSDecimalNumber *expectedDecimalNumber = [NSDecimalNumber decimalNumberWithDecimal:[(NSNumber *) expected decimalValue]];
|
||||
return [actualDecimalNumber isEqualToNumber:expectedDecimalNumber];
|
||||
}
|
||||
else {
|
||||
if(EXPIsNumberFloat((NSNumber *)actual) || EXPIsNumberFloat((NSNumber *)expected)) {
|
||||
return [(NSNumber *)actual floatValue] == [(NSNumber *)expected floatValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
NSString *expectedDescription = EXPDescribeObject(expected);
|
||||
NSString *actualDescription = EXPDescribeObject(actual);
|
||||
|
||||
if (![expectedDescription isEqualToString:actualDescription]) {
|
||||
return [NSString stringWithFormat:@"expected: %@, got: %@", EXPDescribeObject(expected), EXPDescribeObject(actual)];
|
||||
} else {
|
||||
return [NSString stringWithFormat:@"expected (%@): %@, got (%@): %@", NSStringFromClass([expected class]), EXPDescribeObject(expected), NSStringFromClass([actual class]), EXPDescribeObject(actual)];
|
||||
}
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: not %@, got: %@", EXPDescribeObject(expected), EXPDescribeObject(actual)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,10 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(haveCountOf, (NSUInteger expected));
|
||||
EXPMatcherInterface(haveCount, (NSUInteger expected));
|
||||
EXPMatcherInterface(haveACountOf, (NSUInteger expected));
|
||||
EXPMatcherInterface(haveLength, (NSUInteger expected));
|
||||
EXPMatcherInterface(haveLengthOf, (NSUInteger expected));
|
||||
EXPMatcherInterface(haveALengthOf, (NSUInteger expected));
|
||||
|
||||
#define beEmpty() haveCountOf(0)
|
||||
@@ -0,0 +1,42 @@
|
||||
#import "EXPMatchers+haveCountOf.h"
|
||||
|
||||
EXPMatcherImplementationBegin(haveCountOf, (NSUInteger expected)) {
|
||||
BOOL actualIsStringy = [actual isKindOfClass:[NSString class]] || [actual isKindOfClass:[NSAttributedString class]];
|
||||
BOOL actualIsCompatible = actualIsStringy || [actual respondsToSelector:@selector(count)];
|
||||
|
||||
prerequisite(^BOOL{
|
||||
return actualIsCompatible;
|
||||
});
|
||||
|
||||
NSUInteger (^count)(id) = ^(id actual) {
|
||||
if(actualIsStringy) {
|
||||
return [actual length];
|
||||
} else {
|
||||
return [actual count];
|
||||
}
|
||||
};
|
||||
|
||||
match(^BOOL{
|
||||
if(actualIsCompatible) {
|
||||
return count(actual) == expected;
|
||||
}
|
||||
return NO;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSString, NSAttributedString, NSArray, NSSet, NSOrderedSet, or NSDictionary", EXPDescribeObject(actual)];
|
||||
return [NSString stringWithFormat:@"expected %@ to have a count of %zi but got %zi", EXPDescribeObject(actual), expected, count(actual)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if(!actualIsCompatible) return [NSString stringWithFormat:@"%@ is not an instance of NSString, NSAttributedString, NSArray, NSSet, NSOrderedSet, or NSDictionary", EXPDescribeObject(actual)];
|
||||
return [NSString stringWithFormat:@"expected %@ not to have a count of %zi", EXPDescribeObject(actual), expected];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
|
||||
EXPMatcherAliasImplementation(haveCount, haveCountOf, (NSUInteger expected));
|
||||
EXPMatcherAliasImplementation(haveACountOf, haveCountOf, (NSUInteger expected));
|
||||
EXPMatcherAliasImplementation(haveLength, haveCountOf, (NSUInteger expected));
|
||||
EXPMatcherAliasImplementation(haveLengthOf, haveCountOf, (NSUInteger expected));
|
||||
EXPMatcherAliasImplementation(haveALengthOf, haveCountOf, (NSUInteger expected));
|
||||
@@ -0,0 +1,3 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(match, (NSString *expected));
|
||||
@@ -0,0 +1,38 @@
|
||||
#import "EXPMatchers+match.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(match, (NSString *expected)) {
|
||||
BOOL actualIsNil = (actual == nil);
|
||||
BOOL expectedIsNil = (expected == nil);
|
||||
|
||||
__block NSRegularExpression *regex = nil;
|
||||
__block NSError *regexError = nil;
|
||||
|
||||
prerequisite (^BOOL {
|
||||
BOOL nilInput = (actualIsNil || expectedIsNil);
|
||||
if (!nilInput) {
|
||||
regex = [NSRegularExpression regularExpressionWithPattern:expected options:0 error:®exError];
|
||||
}
|
||||
return !nilInput && regex;
|
||||
});
|
||||
|
||||
match(^BOOL {
|
||||
NSRange range = [regex rangeOfFirstMatchInString:actual options:0 range:NSMakeRange(0, [actual length])];
|
||||
return !NSEqualRanges(range, NSMakeRange(NSNotFound, 0));
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if (actualIsNil) return @"the object is nil/null";
|
||||
if (expectedIsNil) return @"the expression is nil/null";
|
||||
if (regexError) return [NSString stringWithFormat:@"unable to create regular expression from given parameter: %@", [regexError localizedDescription]];
|
||||
return [NSString stringWithFormat:@"expected: %@ to match to %@", EXPDescribeObject(actual), expected];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if (actualIsNil) return @"the object is nil/null";
|
||||
if (expectedIsNil) return @"the expression is nil/null";
|
||||
if (regexError) return [NSString stringWithFormat:@"unable to create regular expression from given parameter: %@", [regexError localizedDescription]];
|
||||
return [NSString stringWithFormat:@"expected: %@ not to match to %@", EXPDescribeObject(actual), expected];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,4 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(postNotification, (id expectedNotification));
|
||||
EXPMatcherInterface(notify, (id expectedNotification));
|
||||
@@ -0,0 +1,88 @@
|
||||
#import "EXPMatchers+postNotification.h"
|
||||
|
||||
@implementation NSNotification (EXPEquality)
|
||||
|
||||
- (BOOL)exp_isFunctionallyEqualTo:(NSNotification *)otherNotification
|
||||
{
|
||||
if (![otherNotification isKindOfClass:[NSNotification class]]) return NO;
|
||||
|
||||
BOOL namesMatch = [otherNotification.name isEqualToString:self.name];
|
||||
|
||||
BOOL objectsMatch = YES;
|
||||
if (otherNotification.object || self.object) {
|
||||
objectsMatch = [otherNotification.object isEqual:self.object];
|
||||
}
|
||||
|
||||
BOOL userInfoMatches = YES;
|
||||
if (otherNotification.userInfo || self.userInfo) {
|
||||
userInfoMatches = [otherNotification.userInfo isEqual:self.userInfo];
|
||||
}
|
||||
|
||||
return (namesMatch && objectsMatch && userInfoMatches);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
EXPMatcherImplementationBegin(postNotification, (id expected)){
|
||||
BOOL actualIsNil = (actual == nil);
|
||||
BOOL expectedIsNil = (expected == nil);
|
||||
BOOL isNotification = [expected isKindOfClass:[NSNotification class]];
|
||||
BOOL isName = [expected isKindOfClass:[NSString class]];
|
||||
|
||||
__block NSString *expectedName;
|
||||
__block BOOL expectedNotificationOccurred = NO;
|
||||
__block id observer;
|
||||
|
||||
prerequisite(^BOOL{
|
||||
expectedNotificationOccurred = NO;
|
||||
if (actualIsNil || expectedIsNil) return NO;
|
||||
if (isNotification) {
|
||||
expectedName = [expected name];
|
||||
}else if(isName) {
|
||||
expectedName = expected;
|
||||
}else{
|
||||
return NO;
|
||||
}
|
||||
|
||||
observer = [[NSNotificationCenter defaultCenter] addObserverForName:expectedName object:nil queue:nil usingBlock:^(NSNotification *note){
|
||||
if (isNotification) {
|
||||
expectedNotificationOccurred |= [expected exp_isFunctionallyEqualTo:note];
|
||||
}else{
|
||||
expectedNotificationOccurred = YES;
|
||||
}
|
||||
}];
|
||||
((EXPBasicBlock)actual)();
|
||||
return YES;
|
||||
});
|
||||
|
||||
match(^BOOL{
|
||||
if(expectedNotificationOccurred) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
}
|
||||
return expectedNotificationOccurred;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if (observer) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
}
|
||||
if(actualIsNil) return @"the actual value is nil/null";
|
||||
if(expectedIsNil) return @"the expected value is nil/null";
|
||||
if(!(isNotification || isName)) return @"the actual value is not a notification or string";
|
||||
return [NSString stringWithFormat:@"expected: %@, got: none",expectedName];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if (observer) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:observer];
|
||||
}
|
||||
if(actualIsNil) return @"the actual value is nil/null";
|
||||
if(expectedIsNil) return @"the expected value is nil/null";
|
||||
if(!(isNotification || isName)) return @"the actual value is not a notification or string";
|
||||
return [NSString stringWithFormat:@"expected: none, got: %@", expectedName];
|
||||
});
|
||||
}
|
||||
|
||||
EXPMatcherImplementationEnd
|
||||
|
||||
EXPMatcherAliasImplementation(notify, postNotification, (id expectedNotification))
|
||||
@@ -0,0 +1,4 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(raise, (NSString *expectedExceptionName));
|
||||
#define raiseAny() raise(nil)
|
||||
@@ -0,0 +1,30 @@
|
||||
#import "EXPMatchers+raise.h"
|
||||
#import "EXPDefines.h"
|
||||
|
||||
EXPMatcherImplementationBegin(raise, (NSString *expectedExceptionName)) {
|
||||
__block NSException *exceptionCaught = nil;
|
||||
|
||||
match(^BOOL{
|
||||
BOOL expectedExceptionCaught = NO;
|
||||
@try {
|
||||
((EXPBasicBlock)actual)();
|
||||
} @catch(NSException *e) {
|
||||
exceptionCaught = e;
|
||||
expectedExceptionCaught = (expectedExceptionName == nil) || [[exceptionCaught name] isEqualToString:expectedExceptionName];
|
||||
}
|
||||
return expectedExceptionCaught;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@, got: %@",
|
||||
expectedExceptionName ? expectedExceptionName : @"any exception",
|
||||
exceptionCaught ? [exceptionCaught name] : @"no exception"];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@, got: %@",
|
||||
expectedExceptionName ? [NSString stringWithFormat:@"not %@", expectedExceptionName] : @"no exception",
|
||||
exceptionCaught ? [exceptionCaught name] : @"no exception"];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,3 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(raiseWithReason, (NSString *expectedExceptionName, NSString *expectedReason));
|
||||
@@ -0,0 +1,35 @@
|
||||
#import "EXPMatchers+raiseWithReason.h"
|
||||
#import "EXPDefines.h"
|
||||
|
||||
EXPMatcherImplementationBegin(raiseWithReason, (NSString *expectedExceptionName, NSString *expectedReason)) {
|
||||
__block NSException *exceptionCaught = nil;
|
||||
|
||||
match(^BOOL{
|
||||
BOOL expectedExceptionCaught = NO;
|
||||
@try {
|
||||
((EXPBasicBlock)actual)();
|
||||
} @catch(NSException *e) {
|
||||
exceptionCaught = e;
|
||||
expectedExceptionCaught = (((expectedExceptionName == nil) || [[exceptionCaught name] isEqualToString:expectedExceptionName]) &&
|
||||
((expectedReason == nil) || ([[exceptionCaught reason] isEqualToString:expectedReason])));
|
||||
}
|
||||
return expectedExceptionCaught;
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ (%@), got: %@ (%@)",
|
||||
expectedExceptionName ?: @"any exception",
|
||||
expectedReason ?: @"any reason",
|
||||
exceptionCaught ? [exceptionCaught name] : @"no exception",
|
||||
exceptionCaught ? [exceptionCaught reason] : @""];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
return [NSString stringWithFormat:@"expected: %@ (%@), got: %@ (%@)",
|
||||
expectedExceptionName ? [NSString stringWithFormat:@"not %@", expectedExceptionName] : @"no exception",
|
||||
expectedReason ? [NSString stringWithFormat:@"not '%@'", expectedReason] : @"no reason",
|
||||
exceptionCaught ? [exceptionCaught name] : @"no exception",
|
||||
exceptionCaught ? [exceptionCaught reason] : @"no reason"];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
@@ -0,0 +1,3 @@
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPMatcherInterface(respondTo, (SEL expected));
|
||||
@@ -0,0 +1,28 @@
|
||||
#import "EXPMatchers+respondTo.h"
|
||||
#import "EXPMatcherHelpers.h"
|
||||
|
||||
EXPMatcherImplementationBegin(respondTo, (SEL expected)) {
|
||||
BOOL actualIsNil = (actual == nil);
|
||||
BOOL expectedIsNull = (expected == NULL);
|
||||
|
||||
prerequisite (^BOOL {
|
||||
return !(actualIsNil || expectedIsNull);
|
||||
});
|
||||
|
||||
match(^BOOL {
|
||||
return [actual respondsToSelector:expected];
|
||||
});
|
||||
|
||||
failureMessageForTo(^NSString *{
|
||||
if (actualIsNil) return @"the object is nil/null";
|
||||
if (expectedIsNull) return @"the selector is null";
|
||||
return [NSString stringWithFormat:@"expected: %@ to respond to %@", EXPDescribeObject(actual), NSStringFromSelector(expected)];
|
||||
});
|
||||
|
||||
failureMessageForNotTo(^NSString *{
|
||||
if (actualIsNil) return @"the object is nil/null";
|
||||
if (expectedIsNull) return @"the selector is null";
|
||||
return [NSString stringWithFormat:@"expected: %@ not to respond to %@", EXPDescribeObject(actual), NSStringFromSelector(expected)];
|
||||
});
|
||||
}
|
||||
EXPMatcherImplementationEnd
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
#import "EXPMatchers+beNil.h"
|
||||
#import "EXPMatchers+equal.h"
|
||||
#import "EXPMatchers+beInstanceOf.h"
|
||||
#import "EXPMatchers+beKindOf.h"
|
||||
#import "EXPMatchers+beSubclassOf.h"
|
||||
#import "EXPMatchers+conformTo.h"
|
||||
#import "EXPMatchers+beTruthy.h"
|
||||
#import "EXPMatchers+beFalsy.h"
|
||||
#import "EXPMatchers+contain.h"
|
||||
#import "EXPMatchers+beSupersetOf.h"
|
||||
#import "EXPMatchers+haveCountOf.h"
|
||||
#import "EXPMatchers+beIdenticalTo.h"
|
||||
#import "EXPMatchers+beGreaterThan.h"
|
||||
#import "EXPMatchers+beGreaterThanOrEqualTo.h"
|
||||
#import "EXPMatchers+beLessThan.h"
|
||||
#import "EXPMatchers+beLessThanOrEqualTo.h"
|
||||
#import "EXPMatchers+beInTheRangeOf.h"
|
||||
#import "EXPMatchers+beCloseTo.h"
|
||||
#import "EXPMatchers+raise.h"
|
||||
#import "EXPMatchers+raiseWithReason.h"
|
||||
#import "EXPMatchers+respondTo.h"
|
||||
#import "EXPMatchers+postNotification.h"
|
||||
#import "EXPMatchers+beginWith.h"
|
||||
#import "EXPMatchers+endWith.h"
|
||||
#import "EXPMatchers+match.h"
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSObject (Expecta)
|
||||
|
||||
- (void)recordFailureWithDescription:(NSString *)description
|
||||
inFile:(NSString *)filename
|
||||
atLine:(NSUInteger)lineNumber
|
||||
expected:(BOOL)expected;
|
||||
|
||||
@end
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSValue (Expecta)
|
||||
|
||||
@property (nonatomic) const char *_EXP_objCType;
|
||||
|
||||
@end
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#import "NSValue+Expecta.h"
|
||||
#import <objc/runtime.h>
|
||||
#import "Expecta.h"
|
||||
|
||||
EXPFixCategoriesBug(NSValue_Expecta);
|
||||
|
||||
@implementation NSValue (Expecta)
|
||||
|
||||
static char _EXP_typeKey;
|
||||
|
||||
- (const char *)_EXP_objCType {
|
||||
return [(NSString *)objc_getAssociatedObject(self, &_EXP_typeKey) cStringUsingEncoding:NSASCIIStringEncoding];
|
||||
}
|
||||
|
||||
- (void)set_EXP_objCType:(const char *)_EXP_objCType {
|
||||
objc_setAssociatedObject(self, &_EXP_typeKey,
|
||||
@(_EXP_objCType),
|
||||
OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user