1 /**
  2  * @constructor
  3  * @param {jasmine.Env} env
  4  * @param actual
  5  * @param {jasmine.Spec} spec
  6  */
  7 jasmine.Matchers = function(env, actual, spec, opt_isNot) {
  8   this.env = env;
  9   this.actual = actual;
 10   this.spec = spec;
 11   this.isNot = opt_isNot || false;
 12   this.reportWasCalled_ = false;
 13 };
 14 
 15 // todo: @deprecated as of Jasmine 0.11, remove soon [xw]
 16 jasmine.Matchers.pp = function(str) {
 17   throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
 18 };
 19 
 20 // todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
 21 jasmine.Matchers.prototype.report = function(result, failing_message, details) {
 22   throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
 23 };
 24 
 25 jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
 26   for (var methodName in prototype) {
 27     if (methodName == 'report') continue;
 28     var orig = prototype[methodName];
 29     matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
 30   }
 31 };
 32 
 33 jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
 34   return function() {
 35     var matcherArgs = jasmine.util.argsToArray(arguments);
 36     var result = matcherFunction.apply(this, arguments);
 37 
 38     if (this.isNot) {
 39       result = !result;
 40     }
 41 
 42     if (this.reportWasCalled_) return result;
 43 
 44     var message;
 45     if (!result) {
 46       if (this.message) {
 47         message = this.message.apply(this, arguments);
 48         if (jasmine.isArray_(message)) {
 49           message = message[this.isNot ? 1 : 0];
 50         }
 51       } else {
 52         var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
 53         message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
 54         if (matcherArgs.length > 0) {
 55           for (var i = 0; i < matcherArgs.length; i++) {
 56             if (i > 0) message += ",";
 57             message += " " + jasmine.pp(matcherArgs[i]);
 58           }
 59         }
 60         message += ".";
 61       }
 62     }
 63     var expectationResult = new jasmine.ExpectationResult({
 64       matcherName: matcherName,
 65       passed: result,
 66       expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
 67       actual: this.actual,
 68       message: message
 69     });
 70     this.spec.addMatcherResult(expectationResult);
 71     return jasmine.undefined;
 72   };
 73 };
 74 
 75 
 76 
 77 
 78 /**
 79  * toBe: compares the actual to the expected using ===
 80  * @param expected
 81  */
 82 jasmine.Matchers.prototype.toBe = function(expected) {
 83   return this.actual === expected;
 84 };
 85 
 86 /**
 87  * toNotBe: compares the actual to the expected using !==
 88  * @param expected
 89  * @deprecated as of 1.0. Use not.toBe() instead.
 90  */
 91 jasmine.Matchers.prototype.toNotBe = function(expected) {
 92   return this.actual !== expected;
 93 };
 94 
 95 /**
 96  * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
 97  *
 98  * @param expected
 99  */
100 jasmine.Matchers.prototype.toEqual = function(expected) {
101   return this.env.equals_(this.actual, expected);
102 };
103 
104 /**
105  * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
106  * @param expected
107  * @deprecated as of 1.0. Use not.toNotEqual() instead.
108  */
109 jasmine.Matchers.prototype.toNotEqual = function(expected) {
110   return !this.env.equals_(this.actual, expected);
111 };
112 
113 /**
114  * Matcher that compares the actual to the expected using a regular expression.  Constructs a RegExp, so takes
115  * a pattern or a String.
116  *
117  * @param expected
118  */
119 jasmine.Matchers.prototype.toMatch = function(expected) {
120   return new RegExp(expected).test(this.actual);
121 };
122 
123 /**
124  * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
125  * @param expected
126  * @deprecated as of 1.0. Use not.toMatch() instead.
127  */
128 jasmine.Matchers.prototype.toNotMatch = function(expected) {
129   return !(new RegExp(expected).test(this.actual));
130 };
131 
132 /**
133  * Matcher that compares the actual to jasmine.undefined.
134  */
135 jasmine.Matchers.prototype.toBeDefined = function() {
136   return (this.actual !== jasmine.undefined);
137 };
138 
139 /**
140  * Matcher that compares the actual to jasmine.undefined.
141  */
142 jasmine.Matchers.prototype.toBeUndefined = function() {
143   return (this.actual === jasmine.undefined);
144 };
145 
146 /**
147  * Matcher that compares the actual to null.
148  */
149 jasmine.Matchers.prototype.toBeNull = function() {
150   return (this.actual === null);
151 };
152 
153 /**
154  * Matcher that boolean not-nots the actual.
155  */
156 jasmine.Matchers.prototype.toBeTruthy = function() {
157   return !!this.actual;
158 };
159 
160 
161 /**
162  * Matcher that boolean nots the actual.
163  */
164 jasmine.Matchers.prototype.toBeFalsy = function() {
165   return !this.actual;
166 };
167 
168 
169 /**
170  * Matcher that checks to see if the actual, a Jasmine spy, was called.
171  */
172 jasmine.Matchers.prototype.toHaveBeenCalled = function() {
173   if (arguments.length > 0) {
174     throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
175   }
176 
177   if (!jasmine.isSpy(this.actual)) {
178     throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
179   }
180 
181   this.message = function() {
182     return [
183       "Expected spy " + this.actual.identity + " to have been called.",
184       "Expected spy " + this.actual.identity + " not to have been called."
185     ];
186   };
187 
188   return this.actual.wasCalled;
189 };
190 
191 /** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
192 jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
193 
194 /**
195  * Matcher that checks to see if the actual, a Jasmine spy, was not called.
196  *
197  * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
198  */
199 jasmine.Matchers.prototype.wasNotCalled = function() {
200   if (arguments.length > 0) {
201     throw new Error('wasNotCalled does not take arguments');
202   }
203 
204   if (!jasmine.isSpy(this.actual)) {
205     throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
206   }
207 
208   this.message = function() {
209     return [
210       "Expected spy " + this.actual.identity + " to not have been called.",
211       "Expected spy " + this.actual.identity + " to have been called."
212     ];
213   };
214 
215   return !this.actual.wasCalled;
216 };
217 
218 /**
219  * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
220  *
221  * @example
222  *
223  */
224 jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
225   var expectedArgs = jasmine.util.argsToArray(arguments);
226   if (!jasmine.isSpy(this.actual)) {
227     throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
228   }
229   this.message = function() {
230     if (this.actual.callCount == 0) {
231       // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
232       return [
233         "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
234         "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
235       ];
236     } else {
237       return [
238         "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
239         "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
240       ];
241     }
242   };
243 
244   return this.env.contains_(this.actual.argsForCall, expectedArgs);
245 };
246 
247 /** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
248 jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
249 
250 /** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
251 jasmine.Matchers.prototype.wasNotCalledWith = function() {
252   var expectedArgs = jasmine.util.argsToArray(arguments);
253   if (!jasmine.isSpy(this.actual)) {
254     throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
255   }
256 
257   this.message = function() {
258     return [
259       "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
260       "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
261     ]
262   };
263 
264   return !this.env.contains_(this.actual.argsForCall, expectedArgs);
265 };
266 
267 /**
268  * Matcher that checks that the expected item is an element in the actual Array.
269  *
270  * @param {Object} expected
271  */
272 jasmine.Matchers.prototype.toContain = function(expected) {
273   return this.env.contains_(this.actual, expected);
274 };
275 
276 /**
277  * Matcher that checks that the expected item is NOT an element in the actual Array.
278  *
279  * @param {Object} expected
280  * @deprecated as of 1.0. Use not.toNotContain() instead.
281  */
282 jasmine.Matchers.prototype.toNotContain = function(expected) {
283   return !this.env.contains_(this.actual, expected);
284 };
285 
286 jasmine.Matchers.prototype.toBeLessThan = function(expected) {
287   return this.actual < expected;
288 };
289 
290 jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
291   return this.actual > expected;
292 };
293 
294 /**
295  * Matcher that checks that the expected exception was thrown by the actual.
296  *
297  * @param {String} expected
298  */
299 jasmine.Matchers.prototype.toThrow = function(expected) {
300   var result = false;
301   var exception;
302   if (typeof this.actual != 'function') {
303     throw new Error('Actual is not a function');
304   }
305   try {
306     this.actual();
307   } catch (e) {
308     exception = e;
309   }
310   if (exception) {
311     result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
312   }
313 
314   var not = this.isNot ? "not " : "";
315 
316   this.message = function() {
317     if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
318       return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", exception.message || exception].join(' ');
319     } else {
320       return "Expected function to throw an exception.";
321     }
322   };
323 
324   return result;
325 };
326 
327 jasmine.Matchers.Any = function(expectedClass) {
328   this.expectedClass = expectedClass;
329 };
330 
331 jasmine.Matchers.Any.prototype.matches = function(other) {
332   if (this.expectedClass == String) {
333     return typeof other == 'string' || other instanceof String;
334   }
335 
336   if (this.expectedClass == Number) {
337     return typeof other == 'number' || other instanceof Number;
338   }
339 
340   if (this.expectedClass == Function) {
341     return typeof other == 'function' || other instanceof Function;
342   }
343 
344   if (this.expectedClass == Object) {
345     return typeof other == 'object';
346   }
347 
348   return other instanceof this.expectedClass;
349 };
350 
351 jasmine.Matchers.Any.prototype.toString = function() {
352   return '<jasmine.any(' + this.expectedClass + ')>';
353 };
354 
355