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