      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  * Large or small values here may result in slow test running & "Too much recursion" errors
     17  *
     18  */
     19 jasmine.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   return function() {
     32     if (original.apply) {
     33       return original.apply(base, arguments);
     34     } else {
     35       //IE support
     36       if (base == window) {
     37         return window[name].apply(window, arguments);
     38       }
     39     }
     40   };
     41 };
     42
     43 jasmine.setTimeout = jasmine.bindOriginal_(window, 'setTimeout');
     44 jasmine.clearTimeout = jasmine.bindOriginal_(window, 'clearTimeout');
     45 jasmine.setInterval = jasmine.bindOriginal_(window, 'setInterval');
     46 jasmine.clearInterval = jasmine.bindOriginal_(window, 'clearInterval');
     47
     48 jasmine.MessageResult = function(text) {
     49   this.type = 'MessageResult';
     50   this.text = text;
     51   this.trace = new Error(); // todo: test better
     52 };
     53
     54 jasmine.ExpectationResult = function(passed, message, details) {
     55   this.type = 'ExpectationResult';
     56   this.passed_ = passed;
     57   this.message = message;
     58   this.details = details;
     59   this.trace = new Error(message); // todo: test better
     60 };
     61
     62 jasmine.ExpectationResult.prototype.passed = function () {
     63   return this.passed_;
     64 };
     65
     66 /**
     67  * Getter for the Jasmine environment. Ensures one gets created
     68  */
     69 jasmine.getEnv = function() {
     70   return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
     71 };
     72
     73 /**
     74  * @ignore
     75  * @private
     76  * @param value
     77  * @returns {Boolean}
     78  */
     79 jasmine.isArray_ = function(value) {
     80   return value &&
     81          typeof value === 'object' &&
     82          typeof value.length === 'number' &&
     83          typeof value.splice === 'function' &&
     84          !(value.propertyIsEnumerable('length'));
     85 };
     86
     87 /**
     88  * Pretty printer for expecations.  Takes any object and turns it into a human-readable string.
     89  *
     90  * @param value {Object} an object to be outputted
     91  * @returns {String}
     92  */
     93 jasmine.pp = function(value) {
     94   var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
     95   stringPrettyPrinter.format(value);
     96   return stringPrettyPrinter.string;
     97 };
     98
     99 /**
    100  * Returns true if the object is a DOM Node.
    101  *
    102  * @param {Object} obj object to check
    103  * @returns {Boolean}
    104  */
    105 jasmine.isDomNode = function(obj) {
    106   return obj['nodeType'] > 0;
    107 };
    108
    109 /**
    110  * Returns a matchable 'generic' object of the class type.  For use in expecations of type when values don't matter.
    111  *
    112  * @example
    113  * // don't care about which function is passed in, as long as it's a function
    114  * expect(mySpy).wasCalledWith(jasmine.any(Function));
    115  *
    116  * @param {Class} clazz
    117  * @returns matchable object of the type clazz
    118  */
    119 jasmine.any = function(clazz) {
    120   return new jasmine.Matchers.Any(clazz);
    121 };
    122
    123 /**
    124  * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
    125  *
    126  * Spies should be created in test setup, before expectations.  They can then be checked, using the standard Jasmine
    127  * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
    128  *
    129  * A Spy has the following mehtod: wasCalled, callCount, mostRecentCall, and argsForCall (see docs)
    130  * Spies are torn down at the end of every spec.
    131  *
    132  * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
    133  *
    134  * @example
    135  * // a stub
    136  * var myStub = jasmine.createSpy('myStub');  // can be used anywhere
    137  *
    138  * // spy example
    139  * var foo = {
    140  *   not: function(bool) { return !bool; }
    141  * }
    142  *
    143  * // actual foo.not will not be called, execution stops
    144  * spyOn(foo, 'not');
    145
    146  // foo.not spied upon, execution will continue to implementation
    147  * spyOn(foo, 'not').andCallThrough();
    148  *
    149  * // fake example
    150  * var foo = {
    151  *   not: function(bool) { return !bool; }
    152  * }
    153  *
    154  * // foo.not(val) will return val
    155  * spyOn(foo, 'not').andCallFake(function(value) {return value;});
    156  *
    157  * // mock example
    158  * foo.not(7 == 7);
    159  * expect(foo.not).wasCalled();
    160  * expect(foo.not).wasCalledWith(true);
    161  *
    162  * @constructor
    163  * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
    164  * @param {String} name
    165  */
    166 jasmine.Spy = function(name) {
    167   /**
    168    * The name of the spy, if provided.
    169    */
    170   this.identity = name || 'unknown';
    171   /**
    172    *  Is this Object a spy?
    173    */
    174   this.isSpy = true;
    175   /**
    176    * The acutal function this spy stubs.
    177    */
    178   this.plan = function() {
    179   };
    180   /**
    181    * Tracking of the most recent call to the spy.
    182    * @example
    183    * var mySpy = jasmine.createSpy('foo');
    184    * mySpy(1, 2);
    185    * mySpy.mostRecentCall.args = [1, 2];
    186    */
    187   this.mostRecentCall = {};
    188
    189   /**
    190    * Holds arguments for each call to the spy, indexed by call count
    191    * @example
    192    * var mySpy = jasmine.createSpy('foo');
    193    * mySpy(1, 2);
    194    * mySpy(7, 8);
    195    * mySpy.mostRecentCall.args = [7, 8];
    196    * mySpy.argsForCall[0] = [1, 2];
    197    * mySpy.argsForCall[1] = [7, 8];
    198    */
    199   this.argsForCall = [];
    200   this.calls = [];
    201 };
    202
    203 /**
    204  * Tells a spy to call through to the actual implemenatation.
    205  *
    206  * @example
    207  * var foo = {
    208  *   bar: function() { // do some stuff }
    209  * }
    210  *
    211  * // defining a spy on an existing property: foo.bar
    212  * spyOn(foo, 'bar').andCallThrough();
    213  */
    214 jasmine.Spy.prototype.andCallThrough = function() {
    215   this.plan = this.originalValue;
    216   return this;
    217 };
    218
    219 /**
    220  * For setting the return value of a spy.
    221  *
    222  * @example
    223  * // defining a spy from scratch: foo() returns 'baz'
    224  * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
    225  *
    226  * // defining a spy on an existing property: foo.bar() returns 'baz'
    227  * spyOn(foo, 'bar').andReturn('baz');
    228  *
    229  * @param {Object} value
    230  */
    231 jasmine.Spy.prototype.andReturn = function(value) {
    232   this.plan = function() {
    233     return value;
    234   };
    235   return this;
    236 };
    237
    238 /**
    239  * For throwing an exception when a spy is called.
    240  *
    241  * @example
    242  * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
    243  * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
    244  *
    245  * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
    246  * spyOn(foo, 'bar').andThrow('baz');
    247  *
    248  * @param {String} exceptionMsg
    249  */
    250 jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
    251   this.plan = function() {
    252     throw exceptionMsg;
    253   };
    254   return this;
    255 };
    256
    257 /**
    258  * Calls an alternate implementation when a spy is called.
    259  *
    260  * @example
    261  * var baz = function() {
    262  *   // do some stuff, return something
    263  * }
    264  * // defining a spy from scratch: foo() calls the function baz
    265  * var foo = jasmine.createSpy('spy on foo').andCall(baz);
    266  *
    267  * // defining a spy on an existing property: foo.bar() calls an anonymnous function
    268  * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
    269  *
    270  * @param {Function} fakeFunc
    271  */
    272 jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
    273   this.plan = fakeFunc;
    274   return this;
    275 };
    276
    277 /**
    278  * Resets all of a spy's the tracking variables so that it can be used again.
    279  *
    280  * @example
    281  * spyOn(foo, 'bar');
    282  *
    283  * foo.bar();
    284  *
    285  * expect(foo.bar.callCount).toEqual(1);
    286  *
    287  * foo.bar.reset();
    288  *
    289  * expect(foo.bar.callCount).toEqual(0);
    290  */
    291 jasmine.Spy.prototype.reset = function() {
    292   this.wasCalled = false;
    293   this.callCount = 0;
    294   this.argsForCall = [];
    295   this.calls = [];
    296   this.mostRecentCall = {};
    297 };
    298
    299 jasmine.createSpy = function(name) {
    300
    301   var spyObj = function() {
    302     spyObj.wasCalled = true;
    303     spyObj.callCount++;
    304     var args = jasmine.util.argsToArray(arguments);
    305     spyObj.mostRecentCall.object = this;
    306     spyObj.mostRecentCall.args = args;
    307     spyObj.argsForCall.push(args);
    308     spyObj.calls.push({object: this, args: args});
    309     return spyObj.plan.apply(this, arguments);
    310   };
    311
    312   var spy = new jasmine.Spy(name);
    313
    314   for (var prop in spy) {
    315     spyObj[prop] = spy[prop];
    316   }
    317
    318   spyObj.reset();
    319
    320   return spyObj;
    321 };
    322
    323 /**
    324  * Creates a more complicated spy: an Object that has every property a function that is a spy.  Used for stubbing something
    325  * large in one call.
    326  *
    327  * @param {String} baseName name of spy class
    328  * @param {Array} methodNames array of names of methods to make spies
    329  */
    330 jasmine.createSpyObj = function(baseName, methodNames) {
    331   var obj = {};
    332   for (var i = 0; i < methodNames.length; i++) {
    333     obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
    334   }
    335   return obj;
    336 };
    337
    338 jasmine.log = function(message) {
    339   jasmine.getEnv().currentSpec.log(message);
    340 };
    341
    342 /**
    343  * Function that installs a spy on an existing object's method name.  Used within a Spec to create a spy.
    344  *
    345  * @example
    346  * // spy example
    347  * var foo = {
    348  *   not: function(bool) { return !bool; }
    349  * }
    350  * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
    351  *
    352  * @see jasmine.createSpy
    353  * @param obj
    354  * @param methodName
    355  * @returns a Jasmine spy that can be chained with all spy methods
    356  */
    357 var spyOn = function(obj, methodName) {
    358   return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
    359 };
    360
    361 /**
    362  * Creates a Jasmine spec that will be added to the current suite.
    363  *
    364  * // TODO: pending tests
    365  *
    366  * @example
    367  * it('should be true', function() {
    368  *   expect(true).toEqual(true);
    369  * });
    370  *
    371  * @param {String} desc description of this specification
    372  * @param {Function} func defines the preconditions and expectations of the spec
    373  */
    374 var it = function(desc, func) {
    375   return jasmine.getEnv().it(desc, func);
    376 };
    377
    378 /**
    379  * Creates a <em>disabled</em> Jasmine spec.
    380  *
    381  * A convenience method that allows existing specs to be disabled temporarily during development.
    382  *
    383  * @param {String} desc description of this specification
    384  * @param {Function} func defines the preconditions and expectations of the spec
    385  */
    386 var xit = function(desc, func) {
    387   return jasmine.getEnv().xit(desc, func);
    388 };
    389
    390 /**
    391  * Starts a chain for a Jasmine expectation.
    392  *
    393  * It is passed an Object that is the actual value and should chain to one of the many
    394  * jasmine.Matchers functions.
    395  *
    396  * @param {Object} actual Actual value to test against and expected value
    397  */
    398 var expect = function(actual) {
    399   return jasmine.getEnv().currentSpec.expect(actual);
    400 };
    401
    402 /**
    403  * Defines part of a jasmine spec.  Used in cominbination with waits or waitsFor in asynchrnous specs.
    404  *
    405  * @param {Function} func Function that defines part of a jasmine spec.
    406  */
    407 var runs = function(func) {
    408   jasmine.getEnv().currentSpec.runs(func);
    409 };
    410
    411 /**
    412  * Waits for a timeout before moving to the next runs()-defined block.
    413  * @param {Number} timeout
    414  */
    415 var waits = function(timeout) {
    416   jasmine.getEnv().currentSpec.waits(timeout);
    417 };
    418
    419 /**
    420  * Waits for the latchFunction to return true before proceeding to the next runs()-defined block.
    421  *
    422  * @param {Number} timeout
    423  * @param {Function} latchFunction
    424  * @param {String} message
    425  */
    426 var waitsFor = function(timeout, latchFunction, message) {
    427   jasmine.getEnv().currentSpec.waitsFor(timeout, latchFunction, message);
    428 };
    429
    430 /**
    431  * A function that is called before each spec in a suite.
    432  *
    433  * Used for spec setup, including validating assumptions.
    434  *
    435  * @param {Function} beforeEachFunction
    436  */
    437 var beforeEach = function(beforeEachFunction) {
    438   jasmine.getEnv().beforeEach(beforeEachFunction);
    439 };
    440
    441 /**
    442  * A function that is called after each spec in a suite.
    443  *
    444  * Used for restoring any state that is hijacked during spec execution.
    445  *
    446  * @param {Function} afterEachFunction
    447  */
    448 var afterEach = function(afterEachFunction) {
    449   jasmine.getEnv().afterEach(afterEachFunction);
    450 };
    451
    452 /**
    453  * Defines a suite of specifications.
    454  *
    455  * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
    456  * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
    457  * of setup in some tests.
    458  *
    459  * @example
    460  * // TODO: a simple suite
    461  *
    462  * // TODO: a simple suite with a nested describe block
    463  *
    464  * @param {String} description A string, usually the class under test.
    465  * @param {Function} specDefinitions function that defines several specs.
    466  */
    467 var describe = function(description, specDefinitions) {
    468   return jasmine.getEnv().describe(description, specDefinitions);
    469 };
    470
    471 /**
    472  * Disables a suite of specifications.  Used to disable some suites in a file, or files, temporarily during development.
    473  *
    474  * @param {String} description A string, usually the class under test.
    475  * @param {Function} specDefinitions function that defines several specs.
    476  */
    477 var xdescribe = function(description, specDefinitions) {
    478   return jasmine.getEnv().xdescribe(description, specDefinitions);
    479 };
    480
    481
    482 jasmine.XmlHttpRequest = XMLHttpRequest;
    483
    484 // Provide the XMLHttpRequest class for IE 5.x-6.x:
    485 if (typeof XMLHttpRequest == "undefined") jasmine.XmlHttpRequest = function() {
    486   try {
    487     return new ActiveXObject("Msxml2.XMLHTTP.6.0");
    488   } catch(e) {
    489   }
    490   try {
    491     return new ActiveXObject("Msxml2.XMLHTTP.3.0");
    492   } catch(e) {
    493   }
    494   try {
    495     return new ActiveXObject("Msxml2.XMLHTTP");
    496   } catch(e) {
    497   }
    498   try {
    499     return new ActiveXObject("Microsoft.XMLHTTP");
    500   } catch(e) {
    501   }
    502   throw new Error("This browser does not support XMLHttpRequest.");
    503 };
    504
    505 /**
    506  * Adds suite files to an HTML document so that they are executed, thus adding them to the current
    507  * Jasmine environment.
    508  *
    509  * @param {String} url path to the file to include
    510  * @param {Boolean} opt_global
    511  */
    512 jasmine.include = function(url, opt_global) {
    513   if (opt_global) {
    514     document.write('<script type="text/javascript" src="' + url + '"></' + 'script>');
    515   } else {
    516     var xhr;
    517     try {
    518       xhr = new jasmine.XmlHttpRequest();
    519       xhr.open("GET", url, false);
    520       xhr.send(null);
    521     } catch(e) {
    522       throw new Error("couldn't fetch " + url + ": " + e);
    523     }
    524
    525     return eval(xhr.responseText);
    526   }
    527 };
    528
    529 jasmine.version_= {
    530   "major": 0,
    531   "minor": 9,
    532   "build": 0,
    533   "revision": 1255662021
    534   };
    535 /**
    536  * @namespace
    537  */
    538 jasmine.util = {};
    539
    540 /**
    541  * Declare that a child class inherite it's prototype from the parent class.
    542  *
    543  * @private
    544  * @param {Function} childClass
    545  * @param {Function} parentClass
    546  */
    547 jasmine.util.inherit = function(childClass, parentClass) {
    548   var subclass = function() {
    549   };
    550   subclass.prototype = parentClass.prototype;
    551   childClass.prototype = new subclass;
    552 };
    553
    554 jasmine.util.formatException = function(e) {
    555   var lineNumber;
    556   if (e.line) {
    557     lineNumber = e.line;
    558   }
    559   else if (e.lineNumber) {
    560     lineNumber = e.lineNumber;
    561   }
    562
    563   var file;
    564
    565   if (e.sourceURL) {
    566     file = e.sourceURL;
    567   }
    568   else if (e.fileName) {
    569     file = e.fileName;
    570   }
    571
    572   var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
    573
    574   if (file && lineNumber) {
    575     message += ' in ' + file + ' (line ' + lineNumber + ')';
    576   }
    577
    578   return message;
    579 };
    580
    581 jasmine.util.htmlEscape = function(str) {
    582   if (!str) return str;
    583   return str.replace(/&/g, '&')
    584     .replace(/</g, '<')
    585     .replace(/>/g, '>');
    586 };
    587
    588 jasmine.util.argsToArray = function(args) {
    589   var arrayOfArgs = [];
    590   for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
    591   return arrayOfArgs;
    592 };
    593
    594 /**
    595  * Environment for Jasmine
    596  *
    597  * @constructor
    598  */
    599 jasmine.Env = function() {
    600   this.currentSpec = null;
    601   this.currentSuite = null;
    602   this.currentRunner_ = new jasmine.Runner(this);
    603   this.currentlyRunningTests = false;
    604
    605   this.reporter = new jasmine.MultiReporter();
    606
    607   this.updateInterval = jasmine.UPDATE_INTERVAL
    608   this.lastUpdate = 0;
    609   this.specFilter = function() {
    610     return true;
    611   };
    612
    613   this.nextSpecId_ = 0;
    614   this.nextSuiteId_ = 0;
    615   this.equalityTesters_ = [];
    616 };
    617
    618
    619 jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
    620 jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
    621 jasmine.Env.prototype.setInterval = jasmine.setInterval;
    622 jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
    623
    624 /**
    625  * @returns an object containing jasmine version build info, if set.
    626  */
    627 jasmine.Env.prototype.version = function () {
    628   if (jasmine.version_) {
    629     return jasmine.version_;
    630   } else {
    631     throw new Error('Version not set');
    632   }
    633 };
    634
    635 /**
    636  * @returns a sequential integer starting at 0
    637  */
    638 jasmine.Env.prototype.nextSpecId = function () {
    639   return this.nextSpecId_++;
    640 };
    641
    642 /**
    643  * @returns a sequential integer starting at 0
    644  */
    645 jasmine.Env.prototype.nextSuiteId = function () {
    646   return this.nextSuiteId_++;
    647 };
    648
    649 /**
    650  * Register a reporter to receive status updates from Jasmine.
    651  * @param {jasmine.Reporter} reporter An object which will receive status updates.
    652  */
    653 jasmine.Env.prototype.addReporter = function(reporter) {
    654   this.reporter.addReporter(reporter);
    655 };
    656
    657 jasmine.Env.prototype.execute = function() {
    658   this.currentRunner_.execute();
    659 };
    660
    661 jasmine.Env.prototype.describe = function(description, specDefinitions) {
    662   var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
    663
    664   var parentSuite = this.currentSuite;
    665   if (parentSuite) {
    666     parentSuite.add(suite);
    667   } else {
    668     this.currentRunner_.add(suite);
    669   }
    670
    671   this.currentSuite = suite;
    672
    673   specDefinitions.call(suite);
    674
    675   this.currentSuite = parentSuite;
    676
    677   return suite;
    678 };
    679
    680 jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
    681   if (this.currentSuite) {
    682     this.currentSuite.beforeEach(beforeEachFunction);
    683   } else {
    684     this.currentRunner_.beforeEach(beforeEachFunction);
    685   }
    686 };
    687
    688 jasmine.Env.prototype.currentRunner = function () {
    689   return this.currentRunner_;
    690 };
    691
    692 jasmine.Env.prototype.afterEach = function(afterEachFunction) {
    693   if (this.currentSuite) {
    694     this.currentSuite.afterEach(afterEachFunction);
    695   } else {
    696     this.currentRunner_.afterEach(afterEachFunction);
    697   }
    698
    699 };
    700
    701 jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
    702   return {
    703     execute: function() {
    704     }
    705   };
    706 };
    707
    708 jasmine.Env.prototype.it = function(description, func) {
    709   var spec = new jasmine.Spec(this, this.currentSuite, description);
    710   this.currentSuite.add(spec);
    711   this.currentSpec = spec;
    712
    713   if (func) {
    714     spec.runs(func);
    715   }
    716
    717   return spec;
    718 };
    719
    720 jasmine.Env.prototype.xit = function(desc, func) {
    721   return {
    722     id: this.nextSpecId(),
    723     runs: function() {
    724     }
    725   };
    726 };
    727
    728 jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
    729   if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
    730     return true;
    731   }
    732
    733   a.__Jasmine_been_here_before__ = b;
    734   b.__Jasmine_been_here_before__ = a;
    735
    736   var hasKey = function(obj, keyName) {
    737     return obj != null && obj[keyName] !== undefined;
    738   };
    739
    740   for (var property in b) {
    741     if (!hasKey(a, property) && hasKey(b, property)) {
    742       mismatchKeys.push("expected has key '" + property + "', but missing from <b>actual</b>.");
    743     }
    744   }
    745   for (property in a) {
    746     if (!hasKey(b, property) && hasKey(a, property)) {
    747       mismatchKeys.push("<b>expected</b> missing key '" + property + "', but present in actual.");
    748     }
    749   }
    750   for (property in b) {
    751     if (property == '__Jasmine_been_here_before__') continue;
    752     if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
    753       mismatchValues.push("'" + property + "' was<br /><br />'" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "'<br /><br />in expected, but was<br /><br />'" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "'<br /><br />in actual.<br />");
    754     }
    755   }
    756
    757   if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
    758     mismatchValues.push("arrays were not the same length");
    759   }
    760
    761   delete a.__Jasmine_been_here_before__;
    762   delete b.__Jasmine_been_here_before__;
    763   return (mismatchKeys.length == 0 && mismatchValues.length == 0);
    764 };
    765
    766 jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
    767   mismatchKeys = mismatchKeys || [];
    768   mismatchValues = mismatchValues || [];
    769
    770   if (a === b) return true;
    771
    772   if (a === undefined || a === null || b === undefined || b === null) {
    773     return (a == undefined && b == undefined);
    774   }
    775
    776   if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
    777     return a === b;
    778   }
    779
    780   if (a instanceof Date && b instanceof Date) {
    781     return a.getTime() == b.getTime();
    782   }
    783
    784   if (a instanceof jasmine.Matchers.Any) {
    785     return a.matches(b);
    786   }
    787
    788   if (b instanceof jasmine.Matchers.Any) {
    789     return b.matches(a);
    790   }
    791
    792   if (typeof a === "object" && typeof b === "object") {
    793     return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
    794   }
    795
    796   for (var i = 0; i < this.equalityTesters_.length; i++) {
    797     var equalityTester = this.equalityTesters_[i];
    798     var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
    799     if (result !== undefined) return result;
    800   }
    801
    802   //Straight check
    803   return (a === b);
    804 };
    805
    806 jasmine.Env.prototype.contains_ = function(haystack, needle) {
    807   if (jasmine.isArray_(haystack)) {
    808     for (var i = 0; i < haystack.length; i++) {
    809       if (this.equals_(haystack[i], needle)) return true;
    810     }
    811     return false;
    812   }
    813   return haystack.indexOf(needle) >= 0;
    814 };
    815
    816 jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
    817   this.equalityTesters_.push(equalityTester);
    818 };
    819 /** No-op base class for Jasmine reporters.
    820  *
    821  * @constructor
    822  */
    823 jasmine.Reporter = function() {
    824 };
    825
    826 //noinspection JSUnusedLocalSymbols
    827 jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
    828 };
    829
    830 //noinspection JSUnusedLocalSymbols
    831 jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
    832 };
    833
    834 //noinspection JSUnusedLocalSymbols
    835 jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
    836 };
    837
    838 //noinspection JSUnusedLocalSymbols
    839 jasmine.Reporter.prototype.reportSpecResults = function(spec) {
    840 };
    841
    842 //noinspection JSUnusedLocalSymbols
    843 jasmine.Reporter.prototype.log = function(str) {
    844 };
    845
    846 /**
    847  * Blocks are functions with executable code that make up a spec.
    848  *
    849  * @constructor
    850  * @param {jasmine.Env} env
    851  * @param {Function} func
    852  * @param {jasmine.Spec} spec
    853  */
    854 jasmine.Block = function(env, func, spec) {
    855   this.env = env;
    856   this.func = func;
    857   this.spec = spec;
    858 };
    859
    860 jasmine.Block.prototype.execute = function(onComplete) {
    861   try {
    862     this.func.apply(this.spec);
    863   } catch (e) {
    864     this.spec.fail(e);
    865   }
    866   onComplete();
    867 };
    868 /** JavaScript API reporter.
    869  *
    870  * @constructor
    871  */
    872 jasmine.JsApiReporter = function() {
    873   this.started = false;
    874   this.finished = false;
    875   this.suites_ = [];
    876   this.results_ = {};
    877 };
    878
    879 jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
    880   this.started = true;
    881   var suites = runner.suites();
    882   for (var i = 0; i < suites.length; i++) {
    883     var suite = suites[i];
    884     this.suites_.push(this.summarize_(suite));
    885   }
    886 };
    887
    888 jasmine.JsApiReporter.prototype.suites = function() {
    889   return this.suites_;
    890 };
    891
    892 jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
    893   var isSuite = suiteOrSpec instanceof jasmine.Suite
    894   var summary = {
    895     id: suiteOrSpec.id,
    896     name: suiteOrSpec.description,
    897     type: isSuite ? 'suite' : 'spec',
    898     children: []
    899   };
    900   if (isSuite) {
    901     var specs = suiteOrSpec.specs();
    902     for (var i = 0; i < specs.length; i++) {
    903       summary.children.push(this.summarize_(specs[i]));
    904     }
    905   }
    906   return summary;
    907 };
    908
    909 jasmine.JsApiReporter.prototype.results = function() {
    910   return this.results_;
    911 };
    912
    913 jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
    914   return this.results_[specId];
    915 };
    916
    917 //noinspection JSUnusedLocalSymbols
    918 jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
    919   this.finished = true;
    920 };
    921
    922 //noinspection JSUnusedLocalSymbols
    923 jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
    924 };
    925
    926 //noinspection JSUnusedLocalSymbols
    927 jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
    928   this.results_[spec.id] = {
    929     messages: spec.results().getItems(),
    930     result: spec.results().failedCount > 0 ? "failed" : "passed"
    931   };
    932 };
    933
    934 //noinspection JSUnusedLocalSymbols
    935 jasmine.JsApiReporter.prototype.log = function(str) {
    936 };
    937
    938 jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
    939   var results = {};
    940   for (var i = 0; i < specIds.length; i++) {
    941     var specId = specIds[i];
    942     results[specId] = this.summarizeResult_(this.results_[specId]);
    943   }
    944   return results;
    945 };
    946
    947 jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
    948   var summaryMessages = [];
    949   for (var messageIndex in result.messages) {
    950     var resultMessage = result.messages[messageIndex];
    951     summaryMessages.push({
    952       text: resultMessage.text,
    953       passed: resultMessage.passed ? resultMessage.passed() : true,
    954       type: resultMessage.type,
    955       message: resultMessage.message,
    956       trace: {
    957         stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : undefined
    958       }
    959     });
    960   };
    961
    962   var summaryResult = {
    963     result : result.result,
    964     messages : summaryMessages
    965   };
    966
    967   return summaryResult;
    968 };
    969
    970 jasmine.Matchers = function(env, actual, results) {
    971   this.env = env;
    972   this.actual = actual;
    973   this.passing_message = 'Passed.';
    974   this.results_ = results || new jasmine.NestedResults();
    975 };
    976
    977 jasmine.Matchers.pp = function(str) {
    978   return jasmine.util.htmlEscape(jasmine.pp(str));
    979 };
    980
    981 /** @deprecated */
    982 jasmine.Matchers.prototype.getResults = function() {
    983   return this.results_;
    984 };
    985
    986 jasmine.Matchers.prototype.results = function() {
    987   return this.results_;
    988 };
    989
    990 jasmine.Matchers.prototype.report = function(result, failing_message, details) {
    991   this.results_.addResult(new jasmine.ExpectationResult(result, result ? this.passing_message : failing_message, details));
    992   return result;
    993 };
    994
    995 /**
    996  * Matcher that compares the actual to the expected using ===.
    997  *
    998  * @param expected
    999  */
    1000 jasmine.Matchers.prototype.toBe = function(expected) {
    1001   return this.report(this.actual === expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected)
    1002     + '<br /><br />to be the same object as<br /><br />' + jasmine.Matchers.pp(this.actual)
    1003     + '<br />');
    1004 };
    1005
    1006 /**
    1007  * Matcher that compares the actual to the expected using !==
    1008  * @param expected
    1009  */
    1010 jasmine.Matchers.prototype.toNotBe = function(expected) {
    1011   return this.report(this.actual !== expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected)
    1012     + '<br /><br />to be a different object from actual, but they were the same.');
    1013 };
    1014
    1015 /**
    1016  * Matcher that compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
    1017  *
    1018  * @param expected
    1019  */
    1020 jasmine.Matchers.prototype.toEqual = function(expected) {
    1021   var mismatchKeys = [];
    1022   var mismatchValues = [];
    1023
    1024   var formatMismatches = function(name, array) {
    1025     if (array.length == 0) return '';
    1026     var errorOutput = '<br /><br />Different ' + name + ':<br />';
    1027     for (var i = 0; i < array.length; i++) {
    1028       errorOutput += array[i] + '<br />';
    1029     }
    1030     return errorOutput;
    1031   };
    1032
    1033   return this.report(this.env.equals_(this.actual, expected, mismatchKeys, mismatchValues),
    1034     'Expected<br /><br />' + jasmine.Matchers.pp(expected)
    1035       + '<br /><br />but got<br /><br />' + jasmine.Matchers.pp(this.actual)
    1036       + '<br />'
    1037       + formatMismatches('Keys', mismatchKeys)
    1038       + formatMismatches('Values', mismatchValues), {
    1039     matcherName: 'toEqual', expected: expected, actual: this.actual
    1040   });
    1041 };
    1042 /** @deprecated */
    1043 jasmine.Matchers.prototype.should_equal = jasmine.Matchers.prototype.toEqual;
    1044
    1045 /**
    1046  * Matcher that compares the actual to the expected using the ! of jasmine.Matchers.toEqual
    1047  * @param expected
    1048  */
    1049 jasmine.Matchers.prototype.toNotEqual = function(expected) {
    1050   return this.report(!this.env.equals_(this.actual, expected),
    1051     'Expected ' + jasmine.Matchers.pp(expected) + ' to not equal ' + jasmine.Matchers.pp(this.actual) + ', but it does.');
    1052 };
    1053 /** @deprecated */
    1054 jasmine.Matchers.prototype.should_not_equal = jasmine.Matchers.prototype.toNotEqual;
    1055
    1056 /**
    1057  * Matcher that compares the actual to the expected using a regular expression.  Constructs a RegExp, so takes
    1058  * a pattern or a String.
    1059  *
    1060  * @param reg_exp
    1061  */
    1062 jasmine.Matchers.prototype.toMatch = function(reg_exp) {
    1063   return this.report((new RegExp(reg_exp).test(this.actual)),
    1064     'Expected ' + jasmine.Matchers.pp(this.actual) + ' to match ' + reg_exp + '.');
    1065 };
    1066 /** @deprecated */
    1067 jasmine.Matchers.prototype.should_match = jasmine.Matchers.prototype.toMatch;
    1068
    1069 /**
    1070  * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
    1071  * @param reg_exp
    1072  */
    1073 jasmine.Matchers.prototype.toNotMatch = function(reg_exp) {
    1074   return this.report((!new RegExp(reg_exp).test(this.actual)),
    1075     'Expected ' + jasmine.Matchers.pp(this.actual) + ' to not match ' + reg_exp + '.');
    1076 };
    1077 /** @deprecated */
    1078 jasmine.Matchers.prototype.should_not_match = jasmine.Matchers.prototype.toNotMatch;
    1079
    1080 /**
    1081  * Matcher that compares the acutal to undefined.
    1082  */
    1083 jasmine.Matchers.prototype.toBeDefined = function() {
    1084   return this.report((this.actual !== undefined),
    1085     'Expected a value to be defined but it was undefined.');
    1086 };
    1087 /** @deprecated */
    1088 jasmine.Matchers.prototype.should_be_defined = jasmine.Matchers.prototype.toBeDefined;
    1089
    1090 /**
    1091  * Matcher that compares the actual to null.
    1092  *
    1093  */
    1094 jasmine.Matchers.prototype.toBeNull = function() {
    1095   return this.report((this.actual === null),
    1096     'Expected a value to be null but it was ' + jasmine.Matchers.pp(this.actual) + '.');
    1097 };
    1098 /** @deprecated */
    1099 jasmine.Matchers.prototype.should_be_null = jasmine.Matchers.prototype.toBeNull;
    1100
    1101 /**
    1102  * Matcher that boolean not-nots the actual.
    1103  */
    1104 jasmine.Matchers.prototype.toBeTruthy = function() {
    1105   return this.report(!!this.actual,
    1106     'Expected a value to be truthy but it was ' + jasmine.Matchers.pp(this.actual) + '.');
    1107 };
    1108 /** @deprecated */
    1109 jasmine.Matchers.prototype.should_be_truthy = jasmine.Matchers.prototype.toBeTruthy;
    1110
    1111 /**
    1112  * Matcher that boolean nots the actual.
    1113  */
    1114 jasmine.Matchers.prototype.toBeFalsy = function() {
    1115   return this.report(!this.actual,
    1116     'Expected a value to be falsy but it was ' + jasmine.Matchers.pp(this.actual) + '.');
    1117 };
    1118 /** @deprecated */
    1119 jasmine.Matchers.prototype.should_be_falsy = jasmine.Matchers.prototype.toBeFalsy;
    1120
    1121 /**
    1122  * Matcher that checks to see if the acutal, a Jasmine spy, was called.
    1123  */
    1124 jasmine.Matchers.prototype.wasCalled = function() {
    1125   if (!this.actual || !this.actual.isSpy) {
    1126     return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.');
    1127   }
    1128   if (arguments.length > 0) {
    1129     return this.report(false, 'wasCalled matcher does not take arguments');
    1130   }
    1131   return this.report((this.actual.wasCalled),
    1132     'Expected spy "' + this.actual.identity + '" to have been called, but it was not.');
    1133 };
    1134 /** @deprecated */
    1135 jasmine.Matchers.prototype.was_called = jasmine.Matchers.prototype.wasCalled;
    1136
    1137 /**
    1138  * Matcher that checks to see if the acutal, a Jasmine spy, was not called.
    1139  */
    1140 jasmine.Matchers.prototype.wasNotCalled = function() {
    1141   if (!this.actual || !this.actual.isSpy) {
    1142     return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.');
    1143   }
    1144   return this.report((!this.actual.wasCalled),
    1145     'Expected spy "' + this.actual.identity + '" to not have been called, but it was.');
    1146 };
    1147 /** @deprecated */
    1148 jasmine.Matchers.prototype.was_not_called = jasmine.Matchers.prototype.wasNotCalled;
    1149
    1150 /**
    1151  * Matcher that checks to see if the acutal, a Jasmine spy, was called with a set of parameters.
    1152  *
    1153  * @example
    1154  *
    1155  */
    1156 jasmine.Matchers.prototype.wasCalledWith = function() {
    1157   if (!this.actual || !this.actual.isSpy) {
    1158     return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.', {
    1159       matcherName: 'wasCalledWith'
    1160     });
    1161   }
    1162
    1163   var args = jasmine.util.argsToArray(arguments);
    1164
    1165   return this.report(this.env.contains_(this.actual.argsForCall, args),
    1166     'Expected ' + jasmine.Matchers.pp(this.actual.argsForCall) + ' to contain ' + jasmine.Matchers.pp(args) + ', but it does not.', {
    1167     matcherName: 'wasCalledWith', expected: args, actual: this.actual.argsForCall
    1168   });
    1169 };
    1170
    1171 /**
    1172  * Matcher that checks that the expected item is an element in the actual Array.
    1173  *
    1174  * @param {Object} item
    1175  */
    1176 jasmine.Matchers.prototype.toContain = function(item) {
    1177   return this.report(this.env.contains_(this.actual, item),
    1178     'Expected ' + jasmine.Matchers.pp(this.actual) + ' to contain ' + jasmine.Matchers.pp(item) + ', but it does not.', {
    1179     matcherName: 'toContain', expected: item, actual: this.actual
    1180   });
    1181 };
    1182
    1183 /**
    1184  * Matcher that checks that the expected item is NOT an element in the actual Array.
    1185  *
    1186  * @param {Object} item
    1187  */
    1188 jasmine.Matchers.prototype.toNotContain = function(item) {
    1189   return this.report(!this.env.contains_(this.actual, item),
    1190     'Expected ' + jasmine.Matchers.pp(this.actual) + ' not to contain ' + jasmine.Matchers.pp(item) + ', but it does.');
    1191 };
    1192
    1193 jasmine.Matchers.prototype.toBeLessThan = function(expected) {
    1194   return this.report(this.actual < expected,
    1195     'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be less than ' + jasmine.Matchers.pp(expected) + ', but it was not.');
    1196 };
    1197
    1198 jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
    1199   return this.report(this.actual > expected,
    1200     'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be greater than ' + jasmine.Matchers.pp(expected) + ', but it was not.');
    1201 };
    1202
    1203 /**
    1204  * Matcher that checks that the expected exception was thrown by the actual.
    1205  *
    1206  * @param {String} expectedException
    1207  */
    1208 jasmine.Matchers.prototype.toThrow = function(expectedException) {
    1209   var exception = null;
    1210   try {
    1211     this.actual();
    1212   } catch (e) {
    1213     exception = e;
    1214   }
    1215   if (expectedException !== undefined) {
    1216     if (exception == null) {
    1217       return this.report(false, "Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it did not.");
    1218     }
    1219     return this.report(
    1220       this.env.equals_(
    1221         exception.message || exception,
    1222         expectedException.message || expectedException),
    1223       "Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it threw " + jasmine.Matchers.pp(exception) + ".");
    1224   } else {
    1225     return this.report(exception != null, "Expected function to throw an exception, but it did not.");
    1226   }
    1227 };
    1228
    1229 jasmine.Matchers.Any = function(expectedClass) {
    1230   this.expectedClass = expectedClass;
    1231 };
    1232
    1233 jasmine.Matchers.Any.prototype.matches = function(other) {
    1234   if (this.expectedClass == String) {
    1235     return typeof other == 'string' || other instanceof String;
    1236   }
    1237
    1238   if (this.expectedClass == Number) {
    1239     return typeof other == 'number' || other instanceof Number;
    1240   }
    1241
    1242   if (this.expectedClass == Function) {
    1243     return typeof other == 'function' || other instanceof Function;
    1244   }
    1245
    1246   if (this.expectedClass == Object) {
    1247     return typeof other == 'object';
    1248   }
    1249
    1250   return other instanceof this.expectedClass;
    1251 };
    1252
    1253 jasmine.Matchers.Any.prototype.toString = function() {
    1254   return '<jasmine.any(' + this.expectedClass + ')>';
    1255 };
    1256
    1257 /**
    1258  * @constructor
    1259  */
    1260 jasmine.MultiReporter = function() {
    1261   this.subReporters_ = [];
    1262 };
    1263 jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
    1264
    1265 jasmine.MultiReporter.prototype.addReporter = function(reporter) {
    1266   this.subReporters_.push(reporter);
    1267 };
    1268
    1269 (function() {
    1270   var functionNames = ["reportRunnerStarting", "reportRunnerResults", "reportSuiteResults", "reportSpecResults", "log"];
    1271   for (var i = 0; i < functionNames.length; i++) {
    1272     var functionName = functionNames[i];
    1273     jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
    1274       return function() {
    1275         for (var j = 0; j < this.subReporters_.length; j++) {
    1276           var subReporter = this.subReporters_[j];
    1277           if (subReporter[functionName]) {
    1278             subReporter[functionName].apply(subReporter, arguments);
    1279           }
    1280         }
    1281       };
    1282     })(functionName);
    1283   }
    1284 })();
    1285 /**
    1286  * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
    1287  *
    1288  * @constructor
    1289  */
    1290 jasmine.NestedResults = function() {
    1291   /**
    1292    * The total count of results
    1293    */
    1294   this.totalCount = 0;
    1295   /**
    1296    * Number of passed results
    1297    */
    1298   this.passedCount = 0;
    1299   /**
    1300    * Number of failed results
    1301    */
    1302   this.failedCount = 0;
    1303   /**
    1304    * Was this suite/spec skipped?
    1305    */
    1306   this.skipped = false;
    1307   /**
    1308    * @ignore
    1309    */
    1310   this.items_ = [];
    1311 };
    1312
    1313 /**
    1314  * Roll up the result counts.
    1315  *
    1316  * @param result
    1317  */
    1318 jasmine.NestedResults.prototype.rollupCounts = function(result) {
    1319   this.totalCount += result.totalCount;
    1320   this.passedCount += result.passedCount;
    1321   this.failedCount += result.failedCount;
    1322 };
    1323
    1324 /**
    1325  * Tracks a result's message.
    1326  * @param message
    1327  */
    1328 jasmine.NestedResults.prototype.log = function(message) {
    1329   this.items_.push(new jasmine.MessageResult(message));
    1330 };
    1331
    1332 /**
    1333  * Getter for the results: message & results.
    1334  */
    1335 jasmine.NestedResults.prototype.getItems = function() {
    1336   return this.items_;
    1337 };
    1338
    1339 /**
    1340  * Adds a result, tracking counts (total, passed, & failed)
    1341  * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
    1342  */
    1343 jasmine.NestedResults.prototype.addResult = function(result) {
    1344   if (result.type != 'MessageResult') {
    1345     if (result.items_) {
    1346       this.rollupCounts(result);
    1347     } else {
    1348       this.totalCount++;
    1349       if (result.passed()) {
    1350         this.passedCount++;
    1351       } else {
    1352         this.failedCount++;
    1353       }
    1354     }
    1355   }
    1356   this.items_.push(result);
    1357 };
    1358
    1359 /**
    1360  * @returns {Boolean} True if <b>everything</b> below passed
    1361  */
    1362 jasmine.NestedResults.prototype.passed = function() {
    1363   return this.passedCount === this.totalCount;
    1364 };
    1365 /**
    1366  * Base class for pretty printing for expectation results.
    1367  */
    1368 jasmine.PrettyPrinter = function() {
    1369   this.ppNestLevel_ = 0;
    1370 };
    1371
    1372 /**
    1373  * Formats a value in a nice, human-readable string.
    1374  *
    1375  * @param value
    1376  * @returns {String}
    1377  */
    1378 jasmine.PrettyPrinter.prototype.format = function(value) {
    1379   if (this.ppNestLevel_ > 40) {
    1380     //    return '(jasmine.pp nested too deeply!)';
    1381     throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
    1382   }
    1383
    1384   this.ppNestLevel_++;
    1385   try {
    1386     if (value === undefined) {
    1387       this.emitScalar('undefined');
    1388     } else if (value === null) {
    1389       this.emitScalar('null');
    1390     } else if (value.navigator && value.frames && value.setTimeout) {
    1391       this.emitScalar('<window>');
    1392     } else if (value instanceof jasmine.Matchers.Any) {
    1393       this.emitScalar(value.toString());
    1394     } else if (typeof value === 'string') {
    1395       this.emitString(value);
    1396     } else if (typeof value === 'function') {
    1397       this.emitScalar('Function');
    1398     } else if (typeof value.nodeType === 'number') {
    1399       this.emitScalar('HTMLNode');
    1400     } else if (value instanceof Date) {
    1401       this.emitScalar('Date(' + value + ')');
    1402     } else if (value.__Jasmine_been_here_before__) {
    1403       this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
    1404     } else if (jasmine.isArray_(value) || typeof value == 'object') {
    1405       value.__Jasmine_been_here_before__ = true;
    1406       if (jasmine.isArray_(value)) {
    1407         this.emitArray(value);
    1408       } else {
    1409         this.emitObject(value);
    1410       }
    1411       delete value.__Jasmine_been_here_before__;
    1412     } else {
    1413       this.emitScalar(value.toString());
    1414     }
    1415   } finally {
    1416     this.ppNestLevel_--;
    1417   }
    1418 };
    1419
    1420 jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
    1421   for (var property in obj) {
    1422     if (property == '__Jasmine_been_here_before__') continue;
    1423     fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false);
    1424   }
    1425 };
    1426
    1427 jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
    1428 jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
    1429 jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
    1430 jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
    1431
    1432 jasmine.StringPrettyPrinter = function() {
    1433   jasmine.PrettyPrinter.call(this);
    1434
    1435   this.string = '';
    1436 };
    1437 jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
    1438
    1439 jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
    1440   this.append(value);
    1441 };
    1442
    1443 jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
    1444   this.append("'" + value + "'");
    1445 };
    1446
    1447 jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
    1448   this.append('[ ');
    1449   for (var i = 0; i < array.length; i++) {
    1450     if (i > 0) {
    1451       this.append(', ');
    1452     }
    1453     this.format(array[i]);
    1454   }
    1455   this.append(' ]');
    1456 };
    1457
    1458 jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
    1459   var self = this;
    1460   this.append('{ ');
    1461   var first = true;
    1462
    1463   this.iterateObject(obj, function(property, isGetter) {
    1464     if (first) {
    1465       first = false;
    1466     } else {
    1467       self.append(', ');
    1468     }
    1469
    1470     self.append(property);
    1471     self.append(' : ');
    1472     if (isGetter) {
    1473       self.append('<getter>');
    1474     } else {
    1475       self.format(obj[property]);
    1476     }
    1477   });
    1478
    1479   this.append(' }');
    1480 };
    1481
    1482 jasmine.StringPrettyPrinter.prototype.append = function(value) {
    1483   this.string += value;
    1484 };
    1485 jasmine.Queue = function(env) {
    1486   this.env = env;
    1487   this.blocks = [];
    1488   this.running = false;
    1489   this.index = 0;
    1490   this.offset = 0;
    1491 };
    1492
    1493 jasmine.Queue.prototype.addBefore = function(block) {
    1494   this.blocks.unshift(block);
    1495 };
    1496
    1497 jasmine.Queue.prototype.add = function(block) {
    1498   this.blocks.push(block);
    1499 };
    1500
    1501 jasmine.Queue.prototype.insertNext = function(block) {
    1502   this.blocks.splice((this.index + this.offset + 1), 0, block);
    1503   this.offset++;
    1504 };
    1505
    1506 jasmine.Queue.prototype.start = function(onComplete) {
    1507   this.running = true;
    1508   this.onComplete = onComplete;
    1509   this.next_();
    1510 };
    1511
    1512 jasmine.Queue.prototype.isRunning = function() {
    1513   return this.running;
    1514 };
    1515
    1516 jasmine.Queue.LOOP_DONT_RECURSE = true;
    1517
    1518 jasmine.Queue.prototype.next_ = function() {
    1519   var self = this;
    1520   var goAgain = true;
    1521
    1522   while (goAgain) {
    1523     goAgain = false;
    1524
    1525     if (self.index < self.blocks.length) {
    1526       var calledSynchronously = true;
    1527       var completedSynchronously = false;
    1528
    1529       var onComplete = function () {
    1530         if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
    1531           completedSynchronously = true;
    1532           return;
    1533         }
    1534
    1535         self.offset = 0;
    1536         self.index++;
    1537
    1538         var now = new Date().getTime();
    1539         if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
    1540           self.env.lastUpdate = now;
    1541           self.env.setTimeout(function() {
    1542             self.next_();
    1543           }, 0);
    1544         } else {
    1545           if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
    1546             goAgain = true;
    1547           } else {
    1548             self.next_();
    1549           }
    1550         }
    1551       };
    1552       self.blocks[self.index].execute(onComplete);
    1553
    1554       calledSynchronously = false;
    1555       if (completedSynchronously) {
    1556         onComplete();
    1557       }
    1558
    1559     } else {
    1560       self.running = false;
    1561       if (self.onComplete) {
    1562         self.onComplete();
    1563       }
    1564     }
    1565   }
    1566 };
    1567
    1568 jasmine.Queue.prototype.results = function() {
    1569   var results = new jasmine.NestedResults();
    1570   for (var i = 0; i < this.blocks.length; i++) {
    1571     if (this.blocks[i].results) {
    1572       results.addResult(this.blocks[i].results());
    1573     }
    1574   }
    1575   return results;
    1576 };
    1577
    1578
    1579 /* JasmineReporters.reporter
    1580  *    Base object that will get called whenever a Spec, Suite, or Runner is done.  It is up to
    1581  *    descendants of this object to do something with the results (see json_reporter.js)
    1582  */
    1583 jasmine.Reporters = {};
    1584
    1585 jasmine.Reporters.reporter = function(callbacks) {
    1586   var that = {
    1587     callbacks: callbacks || {},
    1588
    1589     doCallback: function(callback, results) {
    1590       if (callback) {
    1591         callback(results);
    1592       }
    1593     },
    1594
    1595     reportRunnerResults: function(runner) {
    1596       that.doCallback(that.callbacks.runnerCallback, runner);
    1597     },
    1598     reportSuiteResults:  function(suite) {
    1599       that.doCallback(that.callbacks.suiteCallback, suite);
    1600     },
    1601     reportSpecResults:   function(spec) {
    1602       that.doCallback(that.callbacks.specCallback, spec);
    1603     },
    1604     log: function (str) {
    1605       if (console && console.log) console.log(str);
    1606     }
    1607   };
    1608
    1609   return that;
    1610 };
    1611
    1612 /**
    1613  * Runner
    1614  *
    1615  * @constructor
    1616  * @param {jasmine.Env} env
    1617  */
    1618 jasmine.Runner = function(env) {
    1619   var self = this;
    1620   self.env = env;
    1621   self.queue = new jasmine.Queue(env);
    1622   self.before_ = [];
    1623   self.after_ = [];
    1624   self.suites_ = [];
    1625 };
    1626
    1627 jasmine.Runner.prototype.execute = function() {
    1628   var self = this;
    1629   if (self.env.reporter.reportRunnerStarting) {
    1630     self.env.reporter.reportRunnerStarting(this);
    1631   }
    1632   self.queue.start(function () {
    1633     self.finishCallback();
    1634   });
    1635 };
    1636
    1637 jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
    1638   beforeEachFunction.typeName = 'beforeEach';
    1639   this.before_.push(beforeEachFunction);
    1640 };
    1641
    1642 jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
    1643   afterEachFunction.typeName = 'afterEach';
    1644   this.after_.push(afterEachFunction);
    1645 };
    1646
    1647
    1648 jasmine.Runner.prototype.finishCallback = function() {
    1649   this.env.reporter.reportRunnerResults(this);
    1650 };
    1651
    1652 jasmine.Runner.prototype.addSuite = function(suite) {
    1653   this.suites_.push(suite);
    1654 };
    1655
    1656 jasmine.Runner.prototype.add = function(block) {
    1657   if (block instanceof jasmine.Suite) {
    1658     this.addSuite(block);
    1659   }
    1660   this.queue.add(block);
    1661 };
    1662
    1663 /** @deprecated */
    1664 jasmine.Runner.prototype.getAllSuites = function() {
    1665   return this.suites_;
    1666 };
    1667
    1668 jasmine.Runner.prototype.specs = function () {
    1669   var suites = this.suites();
    1670   var specs = [];
    1671   for (var i = 0; i < suites.length; i++) {
    1672     specs = specs.concat(suites[i].specs());
    1673   }
    1674   return specs;
    1675 };
    1676
    1677
    1678 jasmine.Runner.prototype.suites = function() {
    1679   return this.suites_;
    1680 };
    1681
    1682 jasmine.Runner.prototype.results = function() {
    1683   return this.queue.results();
    1684 };
    1685
    1686 /** @deprecated */
    1687 jasmine.Runner.prototype.getResults = function() {
    1688   return this.queue.results();
    1689 };
    1690 /**
    1691  * Internal representation of a Jasmine specification, or test.
    1692  *
    1693  * @constructor
    1694  * @param {jasmine.Env} env
    1695  * @param {jasmine.Suite} suite
    1696  * @param {String} description
    1697  */
    1698 jasmine.Spec = function(env, suite, description) {
    1699   if (!env) {
    1700     throw new Error('jasmine.Env() required');
    1701   }
    1702   ;
    1703   if (!suite) {
    1704     throw new Error('jasmine.Suite() required');
    1705   }
    1706   ;
    1707   var spec = this;
    1708   spec.id = env.nextSpecId ? env.nextSpecId() : null;
    1709   spec.env = env;
    1710   spec.suite = suite;
    1711   spec.description = description;
    1712   spec.queue = new jasmine.Queue(env);
    1713
    1714   spec.afterCallbacks = [];
    1715   spec.spies_ = [];
    1716
    1717   spec.results_ = new jasmine.NestedResults();
    1718   spec.results_.description = description;
    1719   spec.matchersClass = null;
    1720 };
    1721
    1722 jasmine.Spec.prototype.getFullName = function() {
    1723   return this.suite.getFullName() + ' ' + this.description + '.';
    1724 };
    1725
    1726
    1727 jasmine.Spec.prototype.results = function() {
    1728   return this.results_;
    1729 };
    1730
    1731 jasmine.Spec.prototype.log = function(message) {
    1732   return this.results_.log(message);
    1733 };
    1734
    1735 /** @deprecated */
    1736 jasmine.Spec.prototype.getResults = function() {
    1737   return this.results_;
    1738 };
    1739
    1740 jasmine.Spec.prototype.runs = function (func) {
    1741   var block = new jasmine.Block(this.env, func, this);
    1742   this.addToQueue(block);
    1743   return this;
    1744 };
    1745
    1746 jasmine.Spec.prototype.addToQueue = function (block) {
    1747   if (this.queue.isRunning()) {
    1748     this.queue.insertNext(block);
    1749   } else {
    1750     this.queue.add(block);
    1751   }
    1752 };
    1753
    1754 /**
    1755  * @private
    1756  * @deprecated
    1757  */
    1758 jasmine.Spec.prototype.expects_that = function(actual) {
    1759   return this.expect(actual);
    1760 };
    1761
    1762 jasmine.Spec.prototype.expect = function(actual) {
    1763   return new (this.getMatchersClass_())(this.env, actual, this.results_);
    1764 };
    1765
    1766 jasmine.Spec.prototype.waits = function(timeout) {
    1767   var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
    1768   this.addToQueue(waitsFunc);
    1769   return this;
    1770 };
    1771
    1772 jasmine.Spec.prototype.waitsFor = function(timeout, latchFunction, timeoutMessage) {
    1773   var waitsForFunc = new jasmine.WaitsForBlock(this.env, timeout, latchFunction, timeoutMessage, this);
    1774   this.addToQueue(waitsForFunc);
    1775   return this;
    1776 };
    1777
    1778 jasmine.Spec.prototype.fail = function (e) {
    1779   this.results_.addResult(new jasmine.ExpectationResult(false, e ? jasmine.util.formatException(e) : null, null));
    1780 };
    1781
    1782 jasmine.Spec.prototype.getMatchersClass_ = function() {
    1783   return this.matchersClass || jasmine.Matchers;
    1784 };
    1785
    1786 jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
    1787   var parent = this.getMatchersClass_();
    1788   var newMatchersClass = function() {
    1789     parent.apply(this, arguments);
    1790   };
    1791   jasmine.util.inherit(newMatchersClass, parent);
    1792   for (var method in matchersPrototype) {
    1793     newMatchersClass.prototype[method] = matchersPrototype[method];
    1794   }
    1795   this.matchersClass = newMatchersClass;
    1796 };
    1797
    1798 jasmine.Spec.prototype.finishCallback = function() {
    1799   this.env.reporter.reportSpecResults(this);
    1800 };
    1801
    1802 jasmine.Spec.prototype.finish = function(onComplete) {
    1803   this.removeAllSpies();
    1804   this.finishCallback();
    1805   if (onComplete) {
    1806     onComplete();
    1807   }
    1808 };
    1809
    1810 jasmine.Spec.prototype.after = function(doAfter, test) {
    1811
    1812   if (this.queue.isRunning()) {
    1813     this.queue.add(new jasmine.Block(this.env, doAfter, this));
    1814   } else {
    1815     this.afterCallbacks.unshift(doAfter);
    1816   }
    1817 };
    1818
    1819 jasmine.Spec.prototype.execute = function(onComplete) {
    1820   var spec = this;
    1821   if (!spec.env.specFilter(spec)) {
    1822     spec.results_.skipped = true;
    1823     spec.finish(onComplete);
    1824     return;
    1825   }
    1826   this.env.reporter.log('>> Jasmine Running ' + this.suite.description + ' ' + this.description + '...');
    1827
    1828   spec.env.currentSpec = spec;
    1829   spec.env.currentlyRunningTests = true;
    1830
    1831   spec.addBeforesAndAftersToQueue();
    1832
    1833   spec.queue.start(function () {
    1834     spec.finish(onComplete);
    1835   });
    1836   spec.env.currentlyRunningTests = false;
    1837 };
    1838
    1839 jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
    1840   var runner = this.env.currentRunner();
    1841   for (var suite = this.suite; suite; suite = suite.parentSuite) {
    1842     for (var i = 0; i < suite.before_.length; i++) {
    1843       this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
    1844     }
    1845   }
    1846   for (var i = 0; i < runner.before_.length; i++) {
    1847     this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
    1848   }
    1849   for (i = 0; i < this.afterCallbacks.length; i++) {
    1850     this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
    1851   }
    1852   for (suite = this.suite; suite; suite = suite.parentSuite) {
    1853     for (var i = 0; i < suite.after_.length; i++) {
    1854       this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
    1855     }
    1856   }
    1857   for (var i = 0; i < runner.after_.length; i++) {
    1858     this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
    1859   }
    1860 };
    1861
    1862 jasmine.Spec.prototype.explodes = function() {
    1863   throw 'explodes function should not have been called';
    1864 };
    1865
    1866 jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
    1867   if (obj == undefined) {
    1868     throw "spyOn could not find an object to spy upon for " + methodName + "()";
    1869   }
    1870
    1871   if (!ignoreMethodDoesntExist && obj[methodName] === undefined) {
    1872     throw methodName + '() method does not exist';
    1873   }
    1874
    1875   if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
    1876     throw new Error(methodName + ' has already been spied upon');
    1877   }
    1878
    1879   var spyObj = jasmine.createSpy(methodName);
    1880
    1881   this.spies_.push(spyObj);
    1882   spyObj.baseObj = obj;
    1883   spyObj.methodName = methodName;
    1884   spyObj.originalValue = obj[methodName];
    1885
    1886   obj[methodName] = spyObj;
    1887
    1888   return spyObj;
    1889 };
    1890
    1891 jasmine.Spec.prototype.removeAllSpies = function() {
    1892   for (var i = 0; i < this.spies_.length; i++) {
    1893     var spy = this.spies_[i];
    1894     spy.baseObj[spy.methodName] = spy.originalValue;
    1895   }
    1896   this.spies_ = [];
    1897 };
    1898
    1899 /**
    1900  * Internal representation of a Jasmine suite.
    1901  *
    1902  * @constructor
    1903  * @param {jasmine.Env} env
    1904  * @param {String} description
    1905  * @param {Function} specDefinitions
    1906  * @param {jasmine.Suite} parentSuite
    1907  */
    1908 jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
    1909   var self = this;
    1910   self.id = env.nextSuiteId ? env.nextSuiteId() : null;
    1911   self.description = description;
    1912   self.queue = new jasmine.Queue(env);
    1913   self.parentSuite = parentSuite;
    1914   self.env = env;
    1915   self.before_ = [];
    1916   self.after_ = [];
    1917   self.specs_ = [];
    1918 };
    1919
    1920 jasmine.Suite.prototype.getFullName = function() {
    1921   var fullName = this.description;
    1922   for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
    1923     fullName = parentSuite.description + ' ' + fullName;
    1924   }
    1925   return fullName;
    1926 };
    1927
    1928 jasmine.Suite.prototype.finish = function(onComplete) {
    1929   this.env.reporter.reportSuiteResults(this);
    1930   this.finished = true;
    1931   if (typeof(onComplete) == 'function') {
    1932     onComplete();
    1933   }
    1934 };
    1935
    1936 jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
    1937   beforeEachFunction.typeName = 'beforeEach';
    1938   this.before_.push(beforeEachFunction);
    1939 };
    1940
    1941 jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
    1942   afterEachFunction.typeName = 'afterEach';
    1943   this.after_.push(afterEachFunction);
    1944 };
    1945
    1946 /** @deprecated */
    1947 jasmine.Suite.prototype.getResults = function() {
    1948   return this.queue.results();
    1949 };
    1950
    1951 jasmine.Suite.prototype.results = function() {
    1952   return this.queue.results();
    1953 };
    1954
    1955 jasmine.Suite.prototype.add = function(block) {
    1956   if (block instanceof jasmine.Suite) {
    1957     this.env.currentRunner().addSuite(block);
    1958   } else {
    1959     this.specs_.push(block);
    1960   }
    1961   this.queue.add(block);
    1962 };
    1963
    1964 /** @deprecated */
    1965 jasmine.Suite.prototype.specCount = function() {
    1966   return this.specs_.length;
    1967 };
    1968
    1969 jasmine.Suite.prototype.specs = function() {
    1970   return this.specs_;
    1971 };
    1972
    1973 jasmine.Suite.prototype.execute = function(onComplete) {
    1974   var self = this;
    1975   this.queue.start(function () {
    1976     self.finish(onComplete);
    1977   });
    1978 };
    1979 jasmine.WaitsBlock = function(env, timeout, spec) {
    1980   this.timeout = timeout;
    1981   jasmine.Block.call(this, env, null, spec);
    1982 };
    1983
    1984 jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
    1985
    1986 jasmine.WaitsBlock.prototype.execute = function (onComplete) {
    1987   this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
    1988   this.env.setTimeout(function () {
    1989     onComplete();
    1990   }, this.timeout);
    1991 };
    1992 jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
    1993   this.timeout = timeout;
    1994   this.latchFunction = latchFunction;
    1995   this.message = message;
    1996   this.totalTimeSpentWaitingForLatch = 0;
    1997   jasmine.Block.call(this, env, null, spec);
    1998 };
    1999
    2000 jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
    2001
    2002 jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 100;
    2003
    2004 jasmine.WaitsForBlock.prototype.execute = function (onComplete) {
    2005   var self = this;
    2006   self.env.reporter.log('>> Jasmine waiting for ' + (self.message || 'something to happen'));
    2007   var latchFunctionResult;
    2008   try {
    2009     latchFunctionResult = self.latchFunction.apply(self.spec);
    2010   } catch (e) {
    2011     self.spec.fail(e);
    2012     onComplete();
    2013     return;
    2014   }
    2015
    2016   if (latchFunctionResult) {
    2017     onComplete();
    2018   } else if (self.totalTimeSpentWaitingForLatch >= self.timeout) {
    2019     var message = 'timed out after ' + self.timeout + ' msec waiting for ' + (self.message || 'something to happen');
    2020     self.spec.fail({
    2021       name: 'timeout',
    2022       message: message
    2023     });
    2024     self.spec._next();
    2025   } else {
    2026     self.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
    2027     self.env.setTimeout(function () { self.execute(onComplete); }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
    2028   }
    2029 };
    2030 // Mock setTimeout, clearTimeout
    2031 // Contributed by Pivotal Computer Systems, www.pivotalsf.com
    2032
    2033 jasmine.FakeTimer = function() {
    2034   this.reset();
    2035
    2036   var self = this;
    2037   self.setTimeout = function(funcToCall, millis) {
    2038     self.timeoutsMade++;
    2039     self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
    2040     return self.timeoutsMade;
    2041   };
    2042
    2043   self.setInterval = function(funcToCall, millis) {
    2044     self.timeoutsMade++;
    2045     self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
    2046     return self.timeoutsMade;
    2047   };
    2048
    2049   self.clearTimeout = function(timeoutKey) {
    2050     self.scheduledFunctions[timeoutKey] = undefined;
    2051   };
    2052
    2053   self.clearInterval = function(timeoutKey) {
    2054     self.scheduledFunctions[timeoutKey] = undefined;
    2055   };
    2056
    2057 };
    2058
    2059 jasmine.FakeTimer.prototype.reset = function() {
    2060   this.timeoutsMade = 0;
    2061   this.scheduledFunctions = {};
    2062   this.nowMillis = 0;
    2063 };
    2064
    2065 jasmine.FakeTimer.prototype.tick = function(millis) {
    2066   var oldMillis = this.nowMillis;
    2067   var newMillis = oldMillis + millis;
    2068   this.runFunctionsWithinRange(oldMillis, newMillis);
    2069   this.nowMillis = newMillis;
    2070 };
    2071
    2072 jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
    2073   var scheduledFunc;
    2074   var funcsToRun = [];
    2075   for (var timeoutKey in this.scheduledFunctions) {
    2076     scheduledFunc = this.scheduledFunctions[timeoutKey];
    2077     if (scheduledFunc != undefined &&
    2078         scheduledFunc.runAtMillis >= oldMillis &&
    2079         scheduledFunc.runAtMillis <= nowMillis) {
    2080       funcsToRun.push(scheduledFunc);
    2081       this.scheduledFunctions[timeoutKey] = undefined;
    2082     }
    2083   }
    2084
    2085   if (funcsToRun.length > 0) {
    2086     funcsToRun.sort(function(a, b) {
    2087       return a.runAtMillis - b.runAtMillis;
    2088     });
    2089     for (var i = 0; i < funcsToRun.length; ++i) {
    2090       try {
    2091         var funcToRun = funcsToRun[i];
    2092         this.nowMillis = funcToRun.runAtMillis;
    2093         funcToRun.funcToCall();
    2094         if (funcToRun.recurring) {
    2095           this.scheduleFunction(funcToRun.timeoutKey,
    2096             funcToRun.funcToCall,
    2097             funcToRun.millis,
    2098             true);
    2099         }
    2100       } catch(e) {
    2101       }
    2102     }
    2103     this.runFunctionsWithinRange(oldMillis, nowMillis);
    2104   }
    2105 };
    2106
    2107 jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
    2108   this.scheduledFunctions[timeoutKey] = {
    2109     runAtMillis: this.nowMillis + millis,
    2110     funcToCall: funcToCall,
    2111     recurring: recurring,
    2112     timeoutKey: timeoutKey,
    2113     millis: millis
    2114   };
    2115 };
    2116
    2117
    2118 jasmine.Clock = {
    2119   defaultFakeTimer: new jasmine.FakeTimer(),
    2120
    2121   reset: function() {
    2122     jasmine.Clock.assertInstalled();
    2123     jasmine.Clock.defaultFakeTimer.reset();
    2124   },
    2125
    2126   tick: function(millis) {
    2127     jasmine.Clock.assertInstalled();
    2128     jasmine.Clock.defaultFakeTimer.tick(millis);
    2129   },
    2130
    2131   runFunctionsWithinRange: function(oldMillis, nowMillis) {
    2132     jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
    2133   },
    2134
    2135   scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
    2136     jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
    2137   },
    2138
    2139   useMock: function() {
    2140     var spec = jasmine.getEnv().currentSpec;
    2141     spec.after(jasmine.Clock.uninstallMock);
    2142
    2143     jasmine.Clock.installMock();
    2144   },
    2145
    2146   installMock: function() {
    2147     jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
    2148   },
    2149
    2150   uninstallMock: function() {
    2151     jasmine.Clock.assertInstalled();
    2152     jasmine.Clock.installed = jasmine.Clock.real;
    2153   },
    2154
    2155   real: {
    2156     setTimeout: window.setTimeout,
    2157     clearTimeout: window.clearTimeout,
    2158     setInterval: window.setInterval,
    2159     clearInterval: window.clearInterval
    2160   },
    2161
    2162   assertInstalled: function() {
    2163     if (jasmine.Clock.installed != jasmine.Clock.defaultFakeTimer) {
    2164       throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
    2165     }
    2166   },
    2167
    2168   installed: null
    2169 };
    2170 jasmine.Clock.installed = jasmine.Clock.real;
    2171
    2172 //else for IE support
    2173 window.setTimeout = function(funcToCall, millis) {
    2174   if (jasmine.Clock.installed.setTimeout.apply) {
    2175     return jasmine.Clock.installed.setTimeout.apply(this, arguments);
    2176   } else {
    2177     return jasmine.Clock.installed.setTimeout(funcToCall, millis);
    2178   }
    2179 };
    2180
    2181 window.setInterval = function(funcToCall, millis) {
    2182   if (jasmine.Clock.installed.setInterval.apply) {
    2183     return jasmine.Clock.installed.setInterval.apply(this, arguments);
    2184   } else {
    2185     return jasmine.Clock.installed.setInterval(funcToCall, millis);
    2186   }
    2187 };
    2188
    2189 window.clearTimeout = function(timeoutKey) {
    2190   if (jasmine.Clock.installed.clearTimeout.apply) {
    2191     return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
    2192   } else {
    2193     return jasmine.Clock.installed.clearTimeout(timeoutKey);
    2194   }
    2195 };
    2196
    2197 window.clearInterval = function(timeoutKey) {
    2198   if (jasmine.Clock.installed.clearTimeout.apply) {
    2199     return jasmine.Clock.installed.clearInterval.apply(this, arguments);
    2200   } else {
    2201   return jasmine.Clock.installed.clearInterval(timeoutKey);
    2202   }
    2203 };
    2204
    2205