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