// // Licensed under the terms in License.txt // // Copyright 2010 Allen Ding. All rights reserved. // #import "KWHaveMatcher.h" #import "KWCountType.h" #import "KWFormatter.h" #import "KWInvocationCapturer.h" #import "KWObjCUtilities.h" #import "KWStringUtilities.h" static NSString * const MatchVerifierKey = @"MatchVerifierKey"; static NSString * const CountTypeKey = @"CountTypeKey"; static NSString * const CountKey = @"CountKey"; @interface KWHaveMatcher() #pragma mark - Properties @property (nonatomic, assign) KWCountType countType; @property (nonatomic, assign) NSUInteger count; @property (nonatomic, strong) NSInvocation *invocation; @property (nonatomic, assign) NSUInteger actualCount; @end @implementation KWHaveMatcher #pragma mark - Getting Matcher Strings + (NSArray *)matcherStrings { return @[ @"haveCountOf:", @"haveCountOfAtLeast:", @"haveCountOfAtMost:", @"haveLengthOf:", @"haveLengthOfAtLeast:", @"haveLengthOfAtMost:", @"have:itemsForInvocation:", @"haveAtLeast:itemsForInvocation:", @"haveAtMost:itemsForInvocation:", ]; } #pragma mark - Matching - (id)targetObject { if (self.invocation == nil) return self.subject; SEL selector = [self.invocation selector]; if ([self.subject respondsToSelector:selector]) { NSMethodSignature *signature = [self.subject methodSignatureForSelector:selector]; if (!KWObjCTypeIsObject([signature methodReturnType])) [NSException raise:@"KWMatcherEception" format:@"a valid collection was not specified"]; __unsafe_unretained id object = nil; [self.invocation invokeWithTarget:self.subject]; [self.invocation getReturnValue:&object]; return object; } else if (KWSelectorParameterCount(selector) == 0) { return self.subject; } else { return nil; } } - (BOOL)evaluate { id targetObject = [self targetObject]; if ([targetObject respondsToSelector:@selector(count)]) self.actualCount = [targetObject count]; else if ([targetObject respondsToSelector:@selector(length)]) self.actualCount = [targetObject length]; else self.actualCount = 0; switch (self.countType) { case KWCountTypeExact: return self.actualCount == self.count; case KWCountTypeAtLeast: return self.actualCount >= self.count; case KWCountTypeAtMost: return self.actualCount <= self.count; } assert(0 && "should never reach here"); return NO; } #pragma mark - Getting Failure Messages - (NSString *)verbPhrase { switch (self.countType) { case KWCountTypeExact: return @"have"; case KWCountTypeAtLeast: return @"have at least"; case KWCountTypeAtMost: return @"have at most"; } assert(0 && "should never reach here"); return nil; } - (NSString *)itemPhrase { if (self.invocation == nil) return @"items"; else return NSStringFromSelector([self.invocation selector]); } - (NSString *)actualCountPhrase { if (self.actualCount == 1) return @"1 item"; else return [NSString stringWithFormat:@"%u items", (unsigned)self.actualCount]; } - (NSString *)failureMessageForShould { return [NSString stringWithFormat:@"expected subject to %@ %u %@, got %@", [self verbPhrase], (unsigned)self.count, [self itemPhrase], [self actualCountPhrase]]; } - (NSString *)failureMessageForShouldNot { return [NSString stringWithFormat:@"expected subject not to %@ %u %@", [self verbPhrase], (unsigned)self.count, [self itemPhrase]]; } #pragma mark - Description - (NSString *)description { return [NSString stringWithFormat:@"%@ %u %@", [self verbPhrase], (unsigned)self.count, [self itemPhrase]]; } #pragma mark - Configuring Matchers - (void)haveCountOf:(NSUInteger)aCount { self.count = aCount; self.countType = KWCountTypeExact; } - (void)haveLengthOf:(NSUInteger)aCount { [self haveCountOf:aCount]; } - (void)haveCountOfAtLeast:(NSUInteger)aCount { self.count = aCount; self.countType = KWCountTypeAtLeast; } - (void)haveLengthOfAtLeast:(NSUInteger)aCount { [self haveCountOfAtLeast:aCount]; } - (void)haveCountOfAtMost:(NSUInteger)aCount { self.count = aCount; self.countType = KWCountTypeAtMost; } - (void)haveLengthOfAtMost:(NSUInteger)aCount { [self haveCountOfAtMost:aCount]; } - (void)have:(NSUInteger)aCount itemsForInvocation:(NSInvocation *)anInvocation { self.count = aCount; self.countType = KWCountTypeExact; self.invocation = anInvocation; } - (void)haveAtLeast:(NSUInteger)aCount itemsForInvocation:(NSInvocation *)anInvocation { self.count = aCount; self.countType = KWCountTypeAtLeast; self.invocation = anInvocation; } - (void)haveAtMost:(NSUInteger)aCount itemsForInvocation:(NSInvocation *)anInvocation { self.count = aCount; self.countType = KWCountTypeAtMost; self.invocation = anInvocation; } #pragma mark - Capturing Invocations + (NSMethodSignature *)invocationCapturer:(KWInvocationCapturer *)anInvocationCapturer methodSignatureForSelector:(SEL)aSelector { KWMatchVerifier *verifier = (anInvocationCapturer.userInfo)[MatchVerifierKey]; if ([verifier.subject respondsToSelector:aSelector]) return [verifier.subject methodSignatureForSelector:aSelector]; // Arbitrary selectors are allowed as expectation expression terminals when // the subject itself is a collection, so return a dummy method signature. NSString *encoding = KWEncodingForDefaultMethod(); return [NSMethodSignature signatureWithObjCTypes:[encoding UTF8String]]; } + (void)invocationCapturer:(KWInvocationCapturer *)anInvocationCapturer didCaptureInvocation:(NSInvocation *)anInvocation { NSDictionary *userInfo = anInvocationCapturer.userInfo; id verifier = userInfo[MatchVerifierKey]; KWCountType countType = [userInfo[CountTypeKey] unsignedIntegerValue]; NSUInteger count = [userInfo[CountKey] unsignedIntegerValue]; switch (countType) { case KWCountTypeExact: [verifier have:count itemsForInvocation:anInvocation]; break; case KWCountTypeAtLeast: [verifier haveAtLeast:count itemsForInvocation:anInvocation]; break; case KWCountTypeAtMost: [verifier haveAtMost:count itemsForInvocation:anInvocation]; break; } } @end #pragma mark - Verifying @implementation KWMatchVerifier(KWHaveMatcherAdditions) #pragma mark - Invocation Capturing Methods - (NSDictionary *)userInfoForHaveMatcherWithCountType:(KWCountType)aCountType count:(NSUInteger)aCount { return @{MatchVerifierKey: self, CountTypeKey: @(aCountType), CountKey: @(aCount)}; } - (id)have:(NSUInteger)aCount { NSDictionary *userInfo = [self userInfoForHaveMatcherWithCountType:KWCountTypeExact count:aCount]; return [KWInvocationCapturer invocationCapturerWithDelegate:[KWHaveMatcher class] userInfo:userInfo]; } - (id)haveAtLeast:(NSUInteger)aCount { NSDictionary *userInfo = [self userInfoForHaveMatcherWithCountType:KWCountTypeAtLeast count:aCount]; return [KWInvocationCapturer invocationCapturerWithDelegate:[KWHaveMatcher class] userInfo:userInfo]; } - (id)haveAtMost:(NSUInteger)aCount { NSDictionary *userInfo = [self userInfoForHaveMatcherWithCountType:KWCountTypeAtMost count:aCount]; return [KWInvocationCapturer invocationCapturerWithDelegate:[KWHaveMatcher class] userInfo:userInfo]; } @end