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