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