main
  1//
  2// Licensed under the terms in License.txt
  3//
  4// Copyright 2010 Allen Ding. All rights reserved.
  5//
  6
  7#import "KWHaveMatcher.h"
  8#import "KWFormatter.h"
  9#import "KWInvocationCapturer.h"
 10#import "KWObjCUtilities.h"
 11#import "KWStringUtilities.h"
 12
 13static NSString * const MatchVerifierKey = @"MatchVerifierKey";
 14static NSString * const CountTypeKey = @"CountTypeKey";
 15static NSString * const CountKey = @"CountKey";
 16
 17@interface KWHaveMatcher()
 18
 19#pragma mark -
 20#pragma mark Properties
 21
 22@property (nonatomic, readwrite) KWCountType countType;
 23@property (nonatomic, readwrite) NSUInteger count;
 24@property (nonatomic, readwrite, retain) NSInvocation *invocation;
 25@property (nonatomic, readwrite) NSUInteger actualCount;
 26
 27@end
 28
 29@implementation KWHaveMatcher
 30
 31#pragma mark -
 32#pragma mark Initializing
 33
 34- (void)dealloc {
 35    [invocation release];
 36    [super dealloc];
 37}
 38
 39#pragma mark -
 40#pragma mark Properties
 41
 42@synthesize countType;
 43@synthesize count;
 44@synthesize invocation;
 45@synthesize actualCount;
 46
 47#pragma mark -
 48#pragma mark Getting Matcher Strings
 49
 50+ (NSArray *)matcherStrings {
 51    return @[@"haveCountOf:",
 52                                     @"haveCountOfAtLeast:",
 53                                     @"haveCountOfAtMost:",
 54                                     @"have:itemsForInvocation:",
 55                                     @"haveAtLeast:itemsForInvocation:",
 56                                     @"haveAtMost:itemsForInvocation:"];
 57}
 58
 59#pragma mark -
 60#pragma mark Matching
 61
 62- (id)targetObject {
 63    if (self.invocation == nil)
 64        return self.subject;
 65
 66    SEL selector = [self.invocation selector];
 67
 68    if ([self.subject respondsToSelector:selector]) {
 69        NSMethodSignature *signature = [self.subject methodSignatureForSelector:selector];
 70
 71        if (!KWObjCTypeIsObject([signature methodReturnType]))
 72            [NSException raise:@"KWMatcherEception" format:@"a valid collection was not specified"];
 73
 74        id object = nil;
 75        [self.invocation invokeWithTarget:self.subject];
 76        [self.invocation getReturnValue:&object];
 77        return object;
 78    } else if (KWSelectorParameterCount(selector) == 0) {
 79        return self.subject;
 80    } else {
 81        return nil;
 82    }
 83}
 84
 85- (BOOL)evaluate {
 86    id targetObject = [self targetObject];
 87
 88    if ([targetObject respondsToSelector:@selector(count)])
 89        self.actualCount = [targetObject count];
 90    else if ([targetObject respondsToSelector:@selector(length)])
 91        self.actualCount = [targetObject length];
 92    else
 93        self.actualCount = 0;
 94
 95    switch (self.countType) {
 96    case KWCountTypeExact:
 97        return self.actualCount == self.count;
 98    case KWCountTypeAtLeast:
 99        return self.actualCount >= self.count;
100    case KWCountTypeAtMost:
101        return self.actualCount <= self.count;
102    }
103
104    assert(0 && "should never reach here");
105    return NO;
106}
107
108#pragma mark -
109#pragma mark Getting Failure Messages
110
111- (NSString *)verbPhrase {
112    switch (self.countType) {
113        case KWCountTypeExact:
114            return @"have";
115        case KWCountTypeAtLeast:
116            return @"have at least";
117        case KWCountTypeAtMost:
118            return @"have at most";
119    }
120
121    assert(0 && "should never reach here");
122    return nil;
123}
124
125- (NSString *)itemPhrase {
126    if (self.invocation == nil)
127        return @"items";
128    else
129        return NSStringFromSelector([self.invocation selector]);
130}
131
132- (NSString *)actualCountPhrase {
133    if (self.actualCount == 1)
134        return @"1 item";
135    else
136        return [NSString stringWithFormat:@"%u items", (unsigned)self.actualCount];
137}
138
139- (NSString *)failureMessageForShould {
140    return [NSString stringWithFormat:@"expected subject to %@ %u %@, got %@",
141                                      [self verbPhrase],
142                                      (unsigned)self.count,
143                                      [self itemPhrase],
144                                      [self actualCountPhrase]];
145}
146
147- (NSString *)failureMessageForShouldNot {
148    return [NSString stringWithFormat:@"expected subject not to %@ %u %@",
149                                      [self verbPhrase],
150                                      (unsigned)self.count,
151                                      [self itemPhrase]];
152}
153
154#pragma mark -
155#pragma mark Description
156
157- (NSString *)description
158{
159  return [NSString stringWithFormat:@"%@ %u %@", [self verbPhrase], (unsigned)self.count, [self itemPhrase]];
160}
161
162#pragma mark -
163#pragma mark Configuring Matchers
164
165- (void)haveCountOf:(NSUInteger)aCount {
166    self.count = aCount;
167    self.countType = KWCountTypeExact;
168}
169
170- (void)haveCountOfAtLeast:(NSUInteger)aCount {
171    self.count = aCount;
172    self.countType = KWCountTypeAtLeast;
173}
174
175- (void)haveCountOfAtMost:(NSUInteger)aCount {
176    self.count = aCount;
177    self.countType = KWCountTypeAtMost;
178}
179
180- (void)have:(NSUInteger)aCount itemsForInvocation:(NSInvocation *)anInvocation {
181    self.count = aCount;
182    self.countType = KWCountTypeExact;
183    self.invocation = anInvocation;
184}
185
186- (void)haveAtLeast:(NSUInteger)aCount itemsForInvocation:(NSInvocation *)anInvocation {
187    self.count = aCount;
188    self.countType = KWCountTypeAtLeast;
189    self.invocation = anInvocation;
190}
191
192- (void)haveAtMost:(NSUInteger)aCount itemsForInvocation:(NSInvocation *)anInvocation {
193    self.count = aCount;
194    self.countType = KWCountTypeAtMost;
195    self.invocation = anInvocation;
196}
197
198#pragma mark -
199#pragma mark Capturing Invocations
200
201+ (NSMethodSignature *)invocationCapturer:(KWInvocationCapturer *)anInvocationCapturer methodSignatureForSelector:(SEL)aSelector {
202    KWMatchVerifier *verifier = (anInvocationCapturer.userInfo)[MatchVerifierKey];
203
204    if ([verifier.subject respondsToSelector:aSelector])
205        return [verifier.subject methodSignatureForSelector:aSelector];
206
207    // Arbitrary selectors are allowed as expectation expression terminals when
208    // the subject itself is a collection, so return a dummy method signature.
209    NSString *encoding = KWEncodingForVoidMethod();
210    return [NSMethodSignature signatureWithObjCTypes:[encoding UTF8String]];
211}
212
213+ (void)invocationCapturer:(KWInvocationCapturer *)anInvocationCapturer didCaptureInvocation:(NSInvocation *)anInvocation {
214    NSDictionary *userInfo = anInvocationCapturer.userInfo;
215    id verifier = userInfo[MatchVerifierKey];
216    KWCountType countType = [userInfo[CountTypeKey] unsignedIntegerValue];
217    NSUInteger count = [userInfo[CountKey] unsignedIntegerValue];
218
219    switch (countType) {
220        case KWCountTypeExact:
221            [verifier have:count itemsForInvocation:anInvocation];
222            break;
223        case KWCountTypeAtLeast:
224            [verifier haveAtLeast:count itemsForInvocation:anInvocation];
225            break;
226        case KWCountTypeAtMost:
227            [verifier haveAtMost:count itemsForInvocation:anInvocation];
228            break;
229    }
230}
231
232@end
233
234@implementation KWMatchVerifier(KWHaveMatcherAdditions)
235
236#pragma mark -
237#pragma mark Verifying
238
239#pragma mark Invocation Capturing Methods
240
241- (NSDictionary *)userInfoForHaveMatcherWithCountType:(KWCountType)aCountType count:(NSUInteger)aCount {
242    return @{MatchVerifierKey: self,
243                                                      CountTypeKey: @(aCountType),
244                                                      CountKey: @(aCount)};
245}
246
247- (id)have:(NSUInteger)aCount {
248    NSDictionary *userInfo = [self userInfoForHaveMatcherWithCountType:KWCountTypeExact count:aCount];
249    return [KWInvocationCapturer invocationCapturerWithDelegate:[KWHaveMatcher class] userInfo:userInfo];
250}
251
252- (id)haveAtLeast:(NSUInteger)aCount {
253    NSDictionary *userInfo = [self userInfoForHaveMatcherWithCountType:KWCountTypeAtLeast count:aCount];
254    return [KWInvocationCapturer invocationCapturerWithDelegate:[KWHaveMatcher class] userInfo:userInfo];
255}
256
257- (id)haveAtMost:(NSUInteger)aCount {
258    NSDictionary *userInfo = [self userInfoForHaveMatcherWithCountType:KWCountTypeAtMost count:aCount];
259    return [KWInvocationCapturer invocationCapturerWithDelegate:[KWHaveMatcher class] userInfo:userInfo];
260}
261
262@end