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