1 /**
  2  * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
  3  *
  4  * @namespace
  5  */
  6 var jasmine = {};
  7 
  8 /**
  9  * @private
 10  */
 11 jasmine.unimplementedMethod_ = function() {
 12   throw new Error("unimplemented method");
 13 };
 14 
 15 /**
 16  * Default interval for event loop yields. Small values here may result in slow test running. Zero means no updates until all tests have completed.
 17  *
 18  */
 19 jasmine.DEFAULT_UPDATE_INTERVAL = 250;
 20 
 21 /**
 22  * Allows for bound functions to be comapred.  Internal use only.
 23  *
 24  * @ignore
 25  * @private
 26  * @param base {Object} bound 'this' for the function
 27  * @param name {Function} function to find
 28  */
 29 jasmine.bindOriginal_ = function(base, name) {
 30   var original = base[name];
 31   if (original.apply) {
 32     return function() {
 33       return original.apply(base, arguments);
 34     };
 35   } else {
 36     // IE support
 37     return window[name];
 38   }
 39 };
 40 
 41 jasmine.setTimeout = jasmine.bindOriginal_(window, 'setTimeout');
 42 jasmine.clearTimeout = jasmine.bindOriginal_(window, 'clearTimeout');
 43 jasmine.setInterval = jasmine.bindOriginal_(window, 'setInterval');
 44 jasmine.clearInterval = jasmine.bindOriginal_(window, 'clearInterval');
 45 
 46 jasmine.MessageResult = function(text) {
 47   this.type = 'MessageResult';
 48   this.text = text;
 49   this.trace = new Error(); // todo: test better
 50 };
 51 
 52 jasmine.ExpectationResult = function(params) {
 53   this.type = 'ExpectationResult';
 54   this.matcherName = params.matcherName;
 55   this.passed_ = params.passed;
 56   this.expected = params.expected;
 57   this.actual = params.actual;
 58 
 59   /** @deprecated */
 60   this.details = params.details;
 61   
 62   this.message = this.passed_ ? 'Passed.' : params.message;
 63   this.trace = this.passed_ ? '' :
 64     ( params.exception ? params.exception :  new Error(this.message) );
 65 };
 66 
 67 jasmine.ExpectationResult.prototype.passed = function () {
 68   return this.passed_;
 69 };
 70 
 71 /**
 72  * Getter for the Jasmine environment. Ensures one gets created
 73  */
 74 jasmine.getEnv = function() {
 75   return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
 76 };
 77 
 78 /**
 79  * @ignore
 80  * @private
 81  * @param value
 82  * @returns {Boolean}
 83  */
 84 jasmine.isArray_ = function(value) {
 85   return value &&
 86          typeof value === 'object' &&
 87          typeof value.length === 'number' &&
 88          typeof value.splice === 'function' &&
 89          !(value.propertyIsEnumerable('length'));
 90 };
 91 
 92 /**
 93  * Pretty printer for expecations.  Takes any object and turns it into a human-readable string.
 94  *
 95  * @param value {Object} an object to be outputted
 96  * @returns {String}
 97  */
 98 jasmine.pp = function(value) {
 99   var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
100   stringPrettyPrinter.format(value);
101   return stringPrettyPrinter.string;
102 };
103 
104 /**
105  * Returns true if the object is a DOM Node.
106  *
107  * @param {Object} obj object to check
108  * @returns {Boolean}
109  */
110 jasmine.isDomNode = function(obj) {
111   return obj['nodeType'] > 0;
112 };
113 
114 /**
115  * Returns a matchable 'generic' object of the class type.  For use in expecations of type when values don't matter.
116  *
117  * @example
118  * // don't care about which function is passed in, as long as it's a function
119  * expect(mySpy).wasCalledWith(jasmine.any(Function));
120  *
121  * @param {Class} clazz
122  * @returns matchable object of the type clazz
123  */
124 jasmine.any = function(clazz) {
125   return new jasmine.Matchers.Any(clazz);
126 };
127 
128 /**
129  * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
130  *
131  * Spies should be created in test setup, before expectations.  They can then be checked, using the standard Jasmine
132  * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
133  *
134  * A Spy has the following mehtod: wasCalled, callCount, mostRecentCall, and argsForCall (see docs)
135  * Spies are torn down at the end of every spec.
136  *
137  * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
138  *
139  * @example
140  * // a stub
141  * var myStub = jasmine.createSpy('myStub');  // can be used anywhere
142  *
143  * // spy example
144  * var foo = {
145  *   not: function(bool) { return !bool; }
146  * }
147  *
148  * // actual foo.not will not be called, execution stops
149  * spyOn(foo, 'not');
150 
151  // foo.not spied upon, execution will continue to implementation
152  * spyOn(foo, 'not').andCallThrough();
153  *
154  * // fake example
155  * var foo = {
156  *   not: function(bool) { return !bool; }
157  * }
158  *
159  * // foo.not(val) will return val
160  * spyOn(foo, 'not').andCallFake(function(value) {return value;});
161  *
162  * // mock example
163  * foo.not(7 == 7);
164  * expect(foo.not).wasCalled();
165  * expect(foo.not).wasCalledWith(true);
166  *
167  * @constructor
168  * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
169  * @param {String} name
170  */
171 jasmine.Spy = function(name) {
172   /**
173    * The name of the spy, if provided.
174    */
175   this.identity = name || 'unknown';
176   /**
177    *  Is this Object a spy?
178    */
179   this.isSpy = true;
180   /**
181    * The actual function this spy stubs.
182    */
183   this.plan = function() {
184   };
185   /**
186    * Tracking of the most recent call to the spy.
187    * @example
188    * var mySpy = jasmine.createSpy('foo');
189    * mySpy(1, 2);
190    * mySpy.mostRecentCall.args = [1, 2];
191    */
192   this.mostRecentCall = {};
193 
194   /**
195    * Holds arguments for each call to the spy, indexed by call count
196    * @example
197    * var mySpy = jasmine.createSpy('foo');
198    * mySpy(1, 2);
199    * mySpy(7, 8);
200    * mySpy.mostRecentCall.args = [7, 8];
201    * mySpy.argsForCall[0] = [1, 2];
202    * mySpy.argsForCall[1] = [7, 8];
203    */
204   this.argsForCall = [];
205   this.calls = [];
206 };
207 
208 /**
209  * Tells a spy to call through to the actual implemenatation.
210  *
211  * @example
212  * var foo = {
213  *   bar: function() { // do some stuff }
214  * }
215  *
216  * // defining a spy on an existing property: foo.bar
217  * spyOn(foo, 'bar').andCallThrough();
218  */
219 jasmine.Spy.prototype.andCallThrough = function() {
220   this.plan = this.originalValue;
221   return this;
222 };
223 
224 /**
225  * For setting the return value of a spy.
226  *
227  * @example
228  * // defining a spy from scratch: foo() returns 'baz'
229  * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
230  *
231  * // defining a spy on an existing property: foo.bar() returns 'baz'
232  * spyOn(foo, 'bar').andReturn('baz');
233  *
234  * @param {Object} value
235  */
236 jasmine.Spy.prototype.andReturn = function(value) {
237   this.plan = function() {
238     return value;
239   };
240   return this;
241 };
242 
243 /**
244  * For throwing an exception when a spy is called.
245  *
246  * @example
247  * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
248  * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
249  *
250  * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
251  * spyOn(foo, 'bar').andThrow('baz');
252  *
253  * @param {String} exceptionMsg
254  */
255 jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
256   this.plan = function() {
257     throw exceptionMsg;
258   };
259   return this;
260 };
261 
262 /**
263  * Calls an alternate implementation when a spy is called.
264  *
265  * @example
266  * var baz = function() {
267  *   // do some stuff, return something
268  * }
269  * // defining a spy from scratch: foo() calls the function baz
270  * var foo = jasmine.createSpy('spy on foo').andCall(baz);
271  *
272  * // defining a spy on an existing property: foo.bar() calls an anonymnous function
273  * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
274  *
275  * @param {Function} fakeFunc
276  */
277 jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
278   this.plan = fakeFunc;
279   return this;
280 };
281 
282 /**
283  * Resets all of a spy's the tracking variables so that it can be used again.
284  *
285  * @example
286  * spyOn(foo, 'bar');
287  *
288  * foo.bar();
289  *
290  * expect(foo.bar.callCount).toEqual(1);
291  *
292  * foo.bar.reset();
293  *
294  * expect(foo.bar.callCount).toEqual(0);
295  */
296 jasmine.Spy.prototype.reset = function() {
297   this.wasCalled = false;
298   this.callCount = 0;
299   this.argsForCall = [];
300   this.calls = [];
301   this.mostRecentCall = {};
302 };
303 
304 jasmine.createSpy = function(name) {
305 
306   var spyObj = function() {
307     spyObj.wasCalled = true;
308     spyObj.callCount++;
309     var args = jasmine.util.argsToArray(arguments);
310     spyObj.mostRecentCall.object = this;
311     spyObj.mostRecentCall.args = args;
312     spyObj.argsForCall.push(args);
313     spyObj.calls.push({object: this, args: args});
314     return spyObj.plan.apply(this, arguments);
315   };
316 
317   var spy = new jasmine.Spy(name);
318 
319   for (var prop in spy) {
320     spyObj[prop] = spy[prop];
321   }
322 
323   spyObj.reset();
324 
325   return spyObj;
326 };
327 
328 /**
329  * Creates a more complicated spy: an Object that has every property a function that is a spy.  Used for stubbing something
330  * large in one call.
331  *
332  * @param {String} baseName name of spy class
333  * @param {Array} methodNames array of names of methods to make spies
334  */
335 jasmine.createSpyObj = function(baseName, methodNames) {
336   var obj = {};
337   for (var i = 0; i < methodNames.length; i++) {
338     obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
339   }
340   return obj;
341 };
342 
343 jasmine.log = function(message) {
344   jasmine.getEnv().currentSpec.log(message);
345 };
346 
347 /**
348  * Function that installs a spy on an existing object's method name.  Used within a Spec to create a spy.
349  *
350  * @example
351  * // spy example
352  * var foo = {
353  *   not: function(bool) { return !bool; }
354  * }
355  * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
356  *
357  * @see jasmine.createSpy
358  * @param obj
359  * @param methodName
360  * @returns a Jasmine spy that can be chained with all spy methods
361  */
362 var spyOn = function(obj, methodName, ignore) {
363   return jasmine.getEnv().currentSpec.spyOn(obj, methodName, ignore);
364 };
365 
366 /**
367  * Creates a Jasmine spec that will be added to the current suite.
368  *
369  * // TODO: pending tests
370  *
371  * @example
372  * it('should be true', function() {
373  *   expect(true).toEqual(true);
374  * });
375  *
376  * @param {String} desc description of this specification
377  * @param {Function} func defines the preconditions and expectations of the spec
378  */
379 var it = function(desc, func) {
380   return jasmine.getEnv().it(desc, func);
381 };
382 
383 /**
384  * Creates a <em>disabled</em> Jasmine spec.
385  *
386  * A convenience method that allows existing specs to be disabled temporarily during development.
387  *
388  * @param {String} desc description of this specification
389  * @param {Function} func defines the preconditions and expectations of the spec
390  */
391 var xit = function(desc, func) {
392   return jasmine.getEnv().xit(desc, func);
393 };
394 
395 jasmine.pending_ = function(){
396   Error.call(this,"Pending");
397 };
398 jasmine.pending_.prototype = new Error;
399 
400 var pending = function() {
401   throw new jasmine.pending_;
402 };
403 
404 /**
405  * Starts a chain for a Jasmine expectation.
406  *
407  * It is passed an Object that is the actual value and should chain to one of the many
408  * jasmine.Matchers functions.
409  *
410  * @param {Object} actual Actual value to test against and expected value
411  */
412 var expect = function(actual) {
413   return jasmine.getEnv().currentSpec.expect(actual);
414 };
415 
416 /**
417  * Defines part of a jasmine spec.  Used in cominbination with waits or waitsFor in asynchrnous specs.
418  *
419  * @param {Function} func Function that defines part of a jasmine spec.
420  */
421 var runs = function(func) {
422   jasmine.getEnv().currentSpec.runs(func);
423 };
424 
425 /**
426  * Waits for a timeout before moving to the next runs()-defined block.
427  * @param {Number} timeout
428  */
429 var waits = function(timeout) {
430   jasmine.getEnv().currentSpec.waits(timeout);
431 };
432 
433 var anticipate = function(number) {
434   jasmine.getEnv().currentSpec.anticipate(number);
435 };
436 
437 var incomplete = function() {
438   jasmine.getEnv().currentSpec.stop();
439 };
440 
441 var complete = function() {
442   jasmine.getEnv().currentSpec.start();
443 };
444 
445 /**
446  * Waits for the latchFunction to return true before proceeding to the next runs()-defined block.
447  *
448  * @param {Number} timeout
449  * @param {Function} latchFunction
450  * @param {String} message
451  */
452 var waitsFor = function(timeout, latchFunction, message) {
453   jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message);
454 };
455 
456 /**
457  * A function that is called before each spec in a suite.
458  *
459  * Used for spec setup, including validating assumptions.
460  *
461  * @param {Function} beforeEachFunction
462  */
463 var beforeEach = function(beforeEachFunction) {
464   jasmine.getEnv().beforeEach(beforeEachFunction);
465 };
466 
467 /**
468  * A function that is called after each spec in a suite.
469  *
470  * Used for restoring any state that is hijacked during spec execution.
471  *
472  * @param {Function} afterEachFunction
473  */
474 var afterEach = function(afterEachFunction) {
475   jasmine.getEnv().afterEach(afterEachFunction);
476 };
477 
478 /**
479  * Defines a suite of specifications.
480  *
481  * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
482  * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
483  * of setup in some tests.
484  *
485  * @example
486  * // TODO: a simple suite
487  *
488  * // TODO: a simple suite with a nested describe block
489  *
490  * @param {String} description A string, usually the class under test.
491  * @param {Function} specDefinitions function that defines several specs.
492  */
493 var describe = function(description, specDefinitions) {
494   return jasmine.getEnv().describe(description, specDefinitions);
495 };
496 
497 /**
498  * Disables a suite of specifications.  Used to disable some suites in a file, or files, temporarily during development.
499  *
500  * @param {String} description A string, usually the class under test.
501  * @param {Function} specDefinitions function that defines several specs.
502  */
503 var xdescribe = function(description, specDefinitions) {
504   return jasmine.getEnv().xdescribe(description, specDefinitions);
505 };
506 
507 
508 jasmine.XmlHttpRequest = XMLHttpRequest;
509 
510 // Provide the XMLHttpRequest class for IE 5.x-6.x:
511 if (typeof XMLHttpRequest == "undefined") jasmine.XmlHttpRequest = function() {
512   try {
513     return new ActiveXObject("Msxml2.XMLHTTP.6.0");
514   } catch(e) {
515   }
516   try {
517     return new ActiveXObject("Msxml2.XMLHTTP.3.0");
518   } catch(e) {
519   }
520   try {
521     return new ActiveXObject("Msxml2.XMLHTTP");
522   } catch(e) {
523   }
524   try {
525     return new ActiveXObject("Microsoft.XMLHTTP");
526   } catch(e) {
527   }
528   throw new Error("This browser does not support XMLHttpRequest.");
529 };
530 
531 /**
532  * Adds suite files to an HTML document so that they are executed, thus adding them to the current
533  * Jasmine environment.
534  *
535  * @param {String} url path to the file to include
536  * @param {Boolean} opt_global
537  */
538 
539 jasmine._eval = function(__jasmine__) {
540   return (function(){eval(__jasmine__);})();
541 };
542 
543 jasmine.include = function(url, opt_global) {
544   if (opt_global) {
545     document.write('<script type="text/javascript" src="' + url + '"></' + 'script>');
546   } else {
547     var xhr;
548     try {
549       xhr = new jasmine.XmlHttpRequest();
550       xhr.open("GET", url, false);
551       xhr.send(null);
552     } catch(e) {
553       throw new Error("couldn't fetch " + url + ": " + e);
554     }
555 
556     return jasmine._eval(xhr.responseText);
557   }
558 };
559