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 }; 511 /** 512 * @namespace 513 */ 514 jasmine.util = {}; 515 516 /** 517 * Declare that a child class inherite it's prototype from the parent class. 518 * 519 * @private 520 * @param {Function} childClass 521 * @param {Function} parentClass 522 */ 523 jasmine.util.inherit = function(childClass, parentClass) { 524 var subclass = function() { 525 }; 526 subclass.prototype = parentClass.prototype; 527 childClass.prototype = new subclass; 528 }; 529 530 jasmine.util.formatException = function(e) { 531 var lineNumber; 532 if (e.line) { 533 lineNumber = e.line; 534 } 535 else if (e.lineNumber) { 536 lineNumber = e.lineNumber; 537 } 538 539 var file; 540 541 if (e.sourceURL) { 542 file = e.sourceURL; 543 } 544 else if (e.fileName) { 545 file = e.fileName; 546 } 547 548 var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); 549 550 if (file && lineNumber) { 551 message += ' in ' + file + ' (line ' + lineNumber + ')'; 552 } 553 554 return message; 555 }; 556 557 jasmine.util.htmlEscape = function(str) { 558 if (!str) return str; 559 return str.replace(/&/g, '&') 560 .replace(/</g, '<') 561 .replace(/>/g, '>'); 562 }; 563 564 jasmine.util.argsToArray = function(args) { 565 var arrayOfArgs = []; 566 for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); 567 return arrayOfArgs; 568 }; 569 570 /** 571 * Environment for Jasmine 572 * 573 * @constructor 574 */ 575 jasmine.Env = function() { 576 this.currentSpec = null; 577 this.currentSuite = null; 578 this.currentRunner = new jasmine.Runner(this); 579 this.currentlyRunningTests = false; 580 581 this.reporter = new jasmine.MultiReporter(); 582 583 this.updateInterval = 0; 584 this.lastUpdate = 0; 585 this.specFilter = function() { 586 return true; 587 }; 588 589 this.nextSpecId_ = 0; 590 this.equalityTesters_ = []; 591 }; 592 593 594 jasmine.Env.prototype.setTimeout = jasmine.setTimeout; 595 jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; 596 jasmine.Env.prototype.setInterval = jasmine.setInterval; 597 jasmine.Env.prototype.clearInterval = jasmine.clearInterval; 598 599 /** 600 * Register a reporter to receive status updates from Jasmine. 601 * @param {jasmine.Reporter} reporter An object which will receive status updates. 602 */ 603 jasmine.Env.prototype.addReporter = function(reporter) { 604 this.reporter.addReporter(reporter); 605 }; 606 607 jasmine.Env.prototype.execute = function() { 608 this.currentRunner.execute(); 609 }; 610 611 jasmine.Env.prototype.describe = function(description, specDefinitions) { 612 var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); 613 614 var parentSuite = this.currentSuite; 615 if (parentSuite) { 616 parentSuite.specs.push(suite); 617 } else { 618 this.currentRunner.suites.push(suite); 619 } 620 621 this.currentSuite = suite; 622 623 specDefinitions.call(suite); 624 625 this.currentSuite = parentSuite; 626 627 return suite; 628 }; 629 630 jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { 631 this.currentSuite.beforeEach(beforeEachFunction); 632 }; 633 634 jasmine.Env.prototype.afterEach = function(afterEachFunction) { 635 this.currentSuite.afterEach(afterEachFunction); 636 }; 637 638 jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { 639 return { 640 execute: function() { 641 } 642 }; 643 }; 644 645 jasmine.Env.prototype.it = function(description, func) { 646 var spec = new jasmine.Spec(this, this.currentSuite, description); 647 this.currentSuite.specs.push(spec); 648 this.currentSpec = spec; 649 650 if (func) { 651 spec.addToQueue(func); 652 } 653 654 return spec; 655 }; 656 657 jasmine.Env.prototype.xit = function(desc, func) { 658 return { 659 id: this.nextSpecId_++, 660 runs: function() { 661 } 662 }; 663 }; 664 665 jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { 666 if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { 667 return true; 668 } 669 670 a.__Jasmine_been_here_before__ = b; 671 b.__Jasmine_been_here_before__ = a; 672 673 var hasKey = function(obj, keyName) { 674 return obj != null && obj[keyName] !== undefined; 675 }; 676 677 for (var property in b) { 678 if (!hasKey(a, property) && hasKey(b, property)) { 679 mismatchKeys.push("expected has key '" + property + "', but missing from <b>actual</b>."); 680 } 681 } 682 for (property in a) { 683 if (!hasKey(b, property) && hasKey(a, property)) { 684 mismatchKeys.push("<b>expected</b> missing key '" + property + "', but present in actual."); 685 } 686 } 687 for (property in b) { 688 if (property == '__Jasmine_been_here_before__') continue; 689 if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { 690 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 />"); 691 } 692 } 693 694 if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { 695 mismatchValues.push("arrays were not the same length"); 696 } 697 698 delete a.__Jasmine_been_here_before__; 699 delete b.__Jasmine_been_here_before__; 700 return (mismatchKeys.length == 0 && mismatchValues.length == 0); 701 }; 702 703 jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { 704 mismatchKeys = mismatchKeys || []; 705 mismatchValues = mismatchValues || []; 706 707 if (a === b) return true; 708 709 if (a === undefined || a === null || b === undefined || b === null) { 710 return (a == undefined && b == undefined); 711 } 712 713 if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { 714 return a === b; 715 } 716 717 if (a instanceof Date && b instanceof Date) { 718 return a.getTime() == b.getTime(); 719 } 720 721 if (a instanceof jasmine.Matchers.Any) { 722 return a.matches(b); 723 } 724 725 if (b instanceof jasmine.Matchers.Any) { 726 return b.matches(a); 727 } 728 729 if (typeof a === "object" && typeof b === "object") { 730 return this.compareObjects_(a, b, mismatchKeys, mismatchValues); 731 } 732 733 for (var i = 0; i < this.equalityTesters_.length; i++) { 734 var equalityTester = this.equalityTesters_[i]; 735 var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); 736 if (result !== undefined) return result; 737 } 738 739 //Straight check 740 return (a === b); 741 }; 742 743 jasmine.Env.prototype.contains_ = function(haystack, needle) { 744 if (jasmine.isArray_(haystack)) { 745 for (var i = 0; i < haystack.length; i++) { 746 if (this.equals_(haystack[i], needle)) return true; 747 } 748 return false; 749 } 750 return haystack.indexOf(needle) >= 0; 751 }; 752 753 jasmine.Env.prototype.addEqualityTester = function(equalityTester) { 754 this.equalityTesters_.push(equalityTester); 755 }; 756 /** 757 * base for Runner & Suite: allows for a queue of functions to get executed, allowing for 758 * any one action to complete, including asynchronous calls, before going to the next 759 * action. 760 * 761 * @constructor 762 * @param {jasmine.Env} env 763 */ 764 jasmine.ActionCollection = function(env) { 765 this.env = env; 766 this.actions = []; 767 this.index = 0; 768 this.finished = false; 769 }; 770 771 /** 772 * Marks the collection as done & calls the finish callback, if there is one 773 */ 774 jasmine.ActionCollection.prototype.finish = function() { 775 if (this.finishCallback) { 776 this.finishCallback(); 777 } 778 this.finished = true; 779 }; 780 781 /** 782 * Starts executing the queue of functions/actions. 783 */ 784 jasmine.ActionCollection.prototype.execute = function() { 785 if (this.actions.length > 0) { 786 this.next(); 787 } 788 }; 789 790 /** 791 * Gets the current action. 792 */ 793 jasmine.ActionCollection.prototype.getCurrentAction = function() { 794 return this.actions[this.index]; 795 }; 796 797 /** 798 * Executes the next queued function/action. If there are no more in the queue, calls #finish. 799 */ 800 jasmine.ActionCollection.prototype.next = function() { 801 if (this.index >= this.actions.length) { 802 this.finish(); 803 return; 804 } 805 806 var currentAction = this.getCurrentAction(); 807 808 currentAction.execute(this); 809 810 if (currentAction.afterCallbacks) { 811 for (var i = 0; i < currentAction.afterCallbacks.length; i++) { 812 try { 813 currentAction.afterCallbacks[i](); 814 } catch (e) { 815 alert(e); 816 } 817 } 818 } 819 820 this.waitForDone(currentAction); 821 }; 822 823 jasmine.ActionCollection.prototype.waitForDone = function(action) { 824 var self = this; 825 var afterExecute = function afterExecute() { 826 self.index++; 827 self.next(); 828 }; 829 830 if (action.finished) { 831 var now = new Date().getTime(); 832 if (this.env.updateInterval && now - this.env.lastUpdate > this.env.updateInterval) { 833 this.env.lastUpdate = now; 834 this.env.setTimeout(afterExecute, 0); 835 } else { 836 afterExecute(); 837 } 838 return; 839 } 840 841 var id = this.env.setInterval(function() { 842 if (action.finished) { 843 self.env.clearInterval(id); 844 afterExecute(); 845 } 846 }, 150); 847 }; 848 /** No-op base class for Jasmine reporters. 849 * 850 * @constructor 851 */ 852 jasmine.Reporter = function() { 853 }; 854 855 //noinspection JSUnusedLocalSymbols 856 jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { 857 }; 858 859 //noinspection JSUnusedLocalSymbols 860 jasmine.Reporter.prototype.reportRunnerResults = function(runner) { 861 }; 862 863 //noinspection JSUnusedLocalSymbols 864 jasmine.Reporter.prototype.reportSuiteResults = function(suite) { 865 }; 866 867 //noinspection JSUnusedLocalSymbols 868 jasmine.Reporter.prototype.reportSpecResults = function(spec) { 869 }; 870 871 //noinspection JSUnusedLocalSymbols 872 jasmine.Reporter.prototype.log = function (str) { 873 }; 874 875 jasmine.Matchers = function(env, actual, results) { 876 this.env = env; 877 this.actual = actual; 878 this.passing_message = 'Passed.'; 879 this.results = results || new jasmine.NestedResults(); 880 }; 881 882 jasmine.Matchers.pp = function(str) { 883 return jasmine.util.htmlEscape(jasmine.pp(str)); 884 }; 885 886 jasmine.Matchers.prototype.getResults = function() { 887 return this.results; 888 }; 889 890 jasmine.Matchers.prototype.report = function(result, failing_message, details) { 891 this.results.addResult(new jasmine.ExpectationResult(result, result ? this.passing_message : failing_message, details)); 892 return result; 893 }; 894 895 /** 896 * Matcher that compares the actual to the expected using ===. 897 * 898 * @param expected 899 */ 900 jasmine.Matchers.prototype.toBe = function(expected) { 901 return this.report(this.actual === expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected) 902 + '<br /><br />to be the same object as<br /><br />' + jasmine.Matchers.pp(this.actual) 903 + '<br />'); 904 }; 905 906 /** 907 * Matcher that compares the actual to the expected using !== 908 * @param expected 909 */ 910 jasmine.Matchers.prototype.toNotBe = function(expected) { 911 return this.report(this.actual !== expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected) 912 + '<br /><br />to be a different object from actual, but they were the same.'); 913 }; 914 915 /** 916 * Matcher that compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 917 * 918 * @param expected 919 */ 920 jasmine.Matchers.prototype.toEqual = function(expected) { 921 var mismatchKeys = []; 922 var mismatchValues = []; 923 924 var formatMismatches = function(name, array) { 925 if (array.length == 0) return ''; 926 var errorOutput = '<br /><br />Different ' + name + ':<br />'; 927 for (var i = 0; i < array.length; i++) { 928 errorOutput += array[i] + '<br />'; 929 } 930 return errorOutput; 931 }; 932 933 return this.report(this.env.equals_(this.actual, expected, mismatchKeys, mismatchValues), 934 'Expected<br /><br />' + jasmine.Matchers.pp(expected) 935 + '<br /><br />but got<br /><br />' + jasmine.Matchers.pp(this.actual) 936 + '<br />' 937 + formatMismatches('Keys', mismatchKeys) 938 + formatMismatches('Values', mismatchValues), { 939 matcherName: 'toEqual', expected: expected, actual: this.actual 940 }); 941 }; 942 /** @deprecated */ 943 jasmine.Matchers.prototype.should_equal = jasmine.Matchers.prototype.toEqual; 944 945 /** 946 * Matcher that compares the actual to the expected using the ! of jasmine.Matchers.toEqual 947 * @param expected 948 */ 949 jasmine.Matchers.prototype.toNotEqual = function(expected) { 950 return this.report(!this.env.equals_(this.actual, expected), 951 'Expected ' + jasmine.Matchers.pp(expected) + ' to not equal ' + jasmine.Matchers.pp(this.actual) + ', but it does.'); 952 }; 953 /** @deprecated */ 954 jasmine.Matchers.prototype.should_not_equal = jasmine.Matchers.prototype.toNotEqual; 955 956 /** 957 * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 958 * a pattern or a String. 959 * 960 * @param reg_exp 961 */ 962 jasmine.Matchers.prototype.toMatch = function(reg_exp) { 963 return this.report((new RegExp(reg_exp).test(this.actual)), 964 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to match ' + reg_exp + '.'); 965 }; 966 /** @deprecated */ 967 jasmine.Matchers.prototype.should_match = jasmine.Matchers.prototype.toMatch; 968 969 /** 970 * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 971 * @param reg_exp 972 */ 973 jasmine.Matchers.prototype.toNotMatch = function(reg_exp) { 974 return this.report((!new RegExp(reg_exp).test(this.actual)), 975 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to not match ' + reg_exp + '.'); 976 }; 977 /** @deprecated */ 978 jasmine.Matchers.prototype.should_not_match = jasmine.Matchers.prototype.toNotMatch; 979 980 /** 981 * Matcher that compares the acutal to undefined. 982 */ 983 jasmine.Matchers.prototype.toBeDefined = function() { 984 return this.report((this.actual !== undefined), 985 'Expected a value to be defined but it was undefined.'); 986 }; 987 /** @deprecated */ 988 jasmine.Matchers.prototype.should_be_defined = jasmine.Matchers.prototype.toBeDefined; 989 990 /** 991 * Matcher that compares the actual to null. 992 * 993 */ 994 jasmine.Matchers.prototype.toBeNull = function() { 995 return this.report((this.actual === null), 996 'Expected a value to be null but it was ' + jasmine.Matchers.pp(this.actual) + '.'); 997 }; 998 /** @deprecated */ 999 jasmine.Matchers.prototype.should_be_null = jasmine.Matchers.prototype.toBeNull; 1000 1001 /** 1002 * Matcher that boolean not-nots the actual. 1003 */ 1004 jasmine.Matchers.prototype.toBeTruthy = function() { 1005 return this.report(!!this.actual, 1006 'Expected a value to be truthy but it was ' + jasmine.Matchers.pp(this.actual) + '.'); 1007 }; 1008 /** @deprecated */ 1009 jasmine.Matchers.prototype.should_be_truthy = jasmine.Matchers.prototype.toBeTruthy; 1010 1011 /** 1012 * Matcher that boolean nots the actual. 1013 */ 1014 jasmine.Matchers.prototype.toBeFalsy = function() { 1015 return this.report(!this.actual, 1016 'Expected a value to be falsy but it was ' + jasmine.Matchers.pp(this.actual) + '.'); 1017 }; 1018 /** @deprecated */ 1019 jasmine.Matchers.prototype.should_be_falsy = jasmine.Matchers.prototype.toBeFalsy; 1020 1021 /** 1022 * Matcher that checks to see if the acutal, a Jasmine spy, was called. 1023 */ 1024 jasmine.Matchers.prototype.wasCalled = function() { 1025 if (!this.actual || !this.actual.isSpy) { 1026 return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); 1027 } 1028 if (arguments.length > 0) { 1029 return this.report(false, 'wasCalled matcher does not take arguments'); 1030 } 1031 return this.report((this.actual.wasCalled), 1032 'Expected spy "' + this.actual.identity + '" to have been called, but it was not.'); 1033 }; 1034 /** @deprecated */ 1035 jasmine.Matchers.prototype.was_called = jasmine.Matchers.prototype.wasCalled; 1036 1037 /** 1038 * Matcher that checks to see if the acutal, a Jasmine spy, was not called. 1039 */ 1040 jasmine.Matchers.prototype.wasNotCalled = function() { 1041 if (!this.actual || !this.actual.isSpy) { 1042 return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); 1043 } 1044 return this.report((!this.actual.wasCalled), 1045 'Expected spy "' + this.actual.identity + '" to not have been called, but it was.'); 1046 }; 1047 /** @deprecated */ 1048 jasmine.Matchers.prototype.was_not_called = jasmine.Matchers.prototype.wasNotCalled; 1049 1050 /** 1051 * Matcher that checks to see if the acutal, a Jasmine spy, was called with a set of parameters. 1052 * 1053 * @example 1054 * 1055 */ 1056 jasmine.Matchers.prototype.wasCalledWith = function() { 1057 if (!this.actual || !this.actual.isSpy) { 1058 return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.', { 1059 matcherName: 'wasCalledWith' 1060 }); 1061 } 1062 1063 var args = jasmine.util.argsToArray(arguments); 1064 1065 return this.report(this.env.contains_(this.actual.argsForCall, args), 1066 'Expected ' + jasmine.Matchers.pp(this.actual.argsForCall) + ' to contain ' + jasmine.Matchers.pp(args) + ', but it does not.', { 1067 matcherName: 'wasCalledWith', expected: args, actual: this.actual.argsForCall 1068 }); 1069 }; 1070 1071 /** 1072 * Matcher that checks that the expected item is an element in the actual Array. 1073 * 1074 * @param {Object} item 1075 */ 1076 jasmine.Matchers.prototype.toContain = function(item) { 1077 return this.report(this.env.contains_(this.actual, item), 1078 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to contain ' + jasmine.Matchers.pp(item) + ', but it does not.', { 1079 matcherName: 'toContain', expected: item, actual: this.actual 1080 }); 1081 }; 1082 1083 /** 1084 * Matcher that checks that the expected item is NOT an element in the actual Array. 1085 * 1086 * @param {Object} item 1087 */ 1088 jasmine.Matchers.prototype.toNotContain = function(item) { 1089 return this.report(!this.env.contains_(this.actual, item), 1090 'Expected ' + jasmine.Matchers.pp(this.actual) + ' not to contain ' + jasmine.Matchers.pp(item) + ', but it does.'); 1091 }; 1092 1093 /** 1094 * Matcher that checks that the expected exception was thrown by the actual. 1095 * 1096 * @param {String} expectedException 1097 */ 1098 jasmine.Matchers.prototype.toThrow = function(expectedException) { 1099 var exception = null; 1100 try { 1101 this.actual(); 1102 } catch (e) { 1103 exception = e; 1104 } 1105 if (expectedException !== undefined) { 1106 if (exception == null) { 1107 return this.report(false, "Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it did not."); 1108 } 1109 return this.report( 1110 this.env.equals_( 1111 exception.message || exception, 1112 expectedException.message || expectedException), 1113 "Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it threw " + jasmine.Matchers.pp(exception) + "."); 1114 } else { 1115 return this.report(exception != null, "Expected function to throw an exception, but it did not."); 1116 } 1117 }; 1118 1119 jasmine.Matchers.Any = function(expectedClass) { 1120 this.expectedClass = expectedClass; 1121 }; 1122 1123 jasmine.Matchers.Any.prototype.matches = function(other) { 1124 if (this.expectedClass == String) { 1125 return typeof other == 'string' || other instanceof String; 1126 } 1127 1128 if (this.expectedClass == Number) { 1129 return typeof other == 'number' || other instanceof Number; 1130 } 1131 1132 if (this.expectedClass == Function) { 1133 return typeof other == 'function' || other instanceof Function; 1134 } 1135 1136 if (this.expectedClass == Object) { 1137 return typeof other == 'object'; 1138 } 1139 1140 return other instanceof this.expectedClass; 1141 }; 1142 1143 jasmine.Matchers.Any.prototype.toString = function() { 1144 return '<jasmine.any(' + this.expectedClass + ')>'; 1145 }; 1146 1147 // Mock setTimeout, clearTimeout 1148 // Contributed by Pivotal Computer Systems, www.pivotalsf.com 1149 1150 jasmine.FakeTimer = function() { 1151 this.reset(); 1152 1153 var self = this; 1154 self.setTimeout = function(funcToCall, millis) { 1155 self.timeoutsMade++; 1156 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 1157 return self.timeoutsMade; 1158 }; 1159 1160 self.setInterval = function(funcToCall, millis) { 1161 self.timeoutsMade++; 1162 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 1163 return self.timeoutsMade; 1164 }; 1165 1166 self.clearTimeout = function(timeoutKey) { 1167 self.scheduledFunctions[timeoutKey] = undefined; 1168 }; 1169 1170 self.clearInterval = function(timeoutKey) { 1171 self.scheduledFunctions[timeoutKey] = undefined; 1172 }; 1173 1174 }; 1175 1176 jasmine.FakeTimer.prototype.reset = function() { 1177 this.timeoutsMade = 0; 1178 this.scheduledFunctions = {}; 1179 this.nowMillis = 0; 1180 }; 1181 1182 jasmine.FakeTimer.prototype.tick = function(millis) { 1183 var oldMillis = this.nowMillis; 1184 var newMillis = oldMillis + millis; 1185 this.runFunctionsWithinRange(oldMillis, newMillis); 1186 this.nowMillis = newMillis; 1187 }; 1188 1189 jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 1190 var scheduledFunc; 1191 var funcsToRun = []; 1192 for (var timeoutKey in this.scheduledFunctions) { 1193 scheduledFunc = this.scheduledFunctions[timeoutKey]; 1194 if (scheduledFunc != undefined && 1195 scheduledFunc.runAtMillis >= oldMillis && 1196 scheduledFunc.runAtMillis <= nowMillis) { 1197 funcsToRun.push(scheduledFunc); 1198 this.scheduledFunctions[timeoutKey] = undefined; 1199 } 1200 } 1201 1202 if (funcsToRun.length > 0) { 1203 funcsToRun.sort(function(a, b) { 1204 return a.runAtMillis - b.runAtMillis; 1205 }); 1206 for (var i = 0; i < funcsToRun.length; ++i) { 1207 try { 1208 var funcToRun = funcsToRun[i]; 1209 this.nowMillis = funcToRun.runAtMillis; 1210 funcToRun.funcToCall(); 1211 if (funcToRun.recurring) { 1212 this.scheduleFunction(funcToRun.timeoutKey, 1213 funcToRun.funcToCall, 1214 funcToRun.millis, 1215 true); 1216 } 1217 } catch(e) { 1218 } 1219 } 1220 this.runFunctionsWithinRange(oldMillis, nowMillis); 1221 } 1222 }; 1223 1224 jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 1225 this.scheduledFunctions[timeoutKey] = { 1226 runAtMillis: this.nowMillis + millis, 1227 funcToCall: funcToCall, 1228 recurring: recurring, 1229 timeoutKey: timeoutKey, 1230 millis: millis 1231 }; 1232 }; 1233 1234 1235 jasmine.Clock = { 1236 defaultFakeTimer: new jasmine.FakeTimer(), 1237 1238 reset: function() { 1239 jasmine.Clock.assertInstalled(); 1240 jasmine.Clock.defaultFakeTimer.reset(); 1241 }, 1242 1243 tick: function(millis) { 1244 jasmine.Clock.assertInstalled(); 1245 jasmine.Clock.defaultFakeTimer.tick(millis); 1246 }, 1247 1248 runFunctionsWithinRange: function(oldMillis, nowMillis) { 1249 jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 1250 }, 1251 1252 scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 1253 jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 1254 }, 1255 1256 useMock: function() { 1257 var spec = jasmine.getEnv().currentSpec; 1258 spec.after(jasmine.Clock.uninstallMock); 1259 1260 jasmine.Clock.installMock(); 1261 }, 1262 1263 installMock: function() { 1264 jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 1265 }, 1266 1267 uninstallMock: function() { 1268 jasmine.Clock.assertInstalled(); 1269 jasmine.Clock.installed = jasmine.Clock.real; 1270 }, 1271 1272 real: { 1273 setTimeout: window.setTimeout, 1274 clearTimeout: window.clearTimeout, 1275 setInterval: window.setInterval, 1276 clearInterval: window.clearInterval 1277 }, 1278 1279 assertInstalled: function() { 1280 if (jasmine.Clock.installed != jasmine.Clock.defaultFakeTimer) { 1281 throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 1282 } 1283 }, 1284 1285 installed: null 1286 }; 1287 jasmine.Clock.installed = jasmine.Clock.real; 1288 1289 window.setTimeout = function(funcToCall, millis) { 1290 return jasmine.Clock.installed.setTimeout.apply(this, arguments); 1291 }; 1292 1293 window.setInterval = function(funcToCall, millis) { 1294 return jasmine.Clock.installed.setInterval.apply(this, arguments); 1295 }; 1296 1297 window.clearTimeout = function(timeoutKey) { 1298 return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 1299 }; 1300 1301 window.clearInterval = function(timeoutKey) { 1302 return jasmine.Clock.installed.clearInterval.apply(this, arguments); 1303 }; 1304 1305 /** 1306 * @constructor 1307 */ 1308 jasmine.MultiReporter = function() { 1309 this.subReporters_ = []; 1310 }; 1311 jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1312 1313 jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1314 this.subReporters_.push(reporter); 1315 }; 1316 1317 (function() { 1318 var functionNames = ["reportRunnerStarting", "reportRunnerResults", "reportSuiteResults", "reportSpecResults", "log"]; 1319 for (var i = 0; i < functionNames.length; i++) { 1320 var functionName = functionNames[i]; 1321 jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1322 return function() { 1323 for (var j = 0; j < this.subReporters_.length; j++) { 1324 var subReporter = this.subReporters_[j]; 1325 if (subReporter[functionName]) { 1326 subReporter[functionName].apply(subReporter, arguments); 1327 } 1328 } 1329 }; 1330 })(functionName); 1331 } 1332 })(); 1333 /** 1334 * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1335 * 1336 * @constructor 1337 */ 1338 jasmine.NestedResults = function() { 1339 /** 1340 * The total count of results 1341 */ 1342 this.totalCount = 0; 1343 /** 1344 * Number of passed results 1345 */ 1346 this.passedCount = 0; 1347 /** 1348 * Number of failed results 1349 */ 1350 this.failedCount = 0; 1351 /** 1352 * Was this suite/spec skipped? 1353 */ 1354 this.skipped = false; 1355 /** 1356 * @ignore 1357 */ 1358 this.items_ = []; 1359 }; 1360 1361 /** 1362 * Roll up the result counts. 1363 * 1364 * @param result 1365 */ 1366 jasmine.NestedResults.prototype.rollupCounts = function(result) { 1367 this.totalCount += result.totalCount; 1368 this.passedCount += result.passedCount; 1369 this.failedCount += result.failedCount; 1370 }; 1371 1372 /** 1373 * Tracks a result's message. 1374 * @param message 1375 */ 1376 jasmine.NestedResults.prototype.log = function(message) { 1377 this.items_.push(new jasmine.MessageResult(message)); 1378 }; 1379 1380 /** 1381 * Getter for the results: message & results. 1382 */ 1383 jasmine.NestedResults.prototype.getItems = function() { 1384 return this.items_; 1385 }; 1386 1387 /** 1388 * Adds a result, tracking counts (total, passed, & failed) 1389 * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1390 */ 1391 jasmine.NestedResults.prototype.addResult = function(result) { 1392 if (result.type != 'MessageResult') { 1393 if (result.items_) { 1394 this.rollupCounts(result); 1395 } else { 1396 this.totalCount++; 1397 if (result.passed) { 1398 this.passedCount++; 1399 } else { 1400 this.failedCount++; 1401 } 1402 } 1403 } 1404 this.items_.push(result); 1405 }; 1406 1407 /** 1408 * @returns {Boolean} True if <b>everything</b> below passed 1409 */ 1410 jasmine.NestedResults.prototype.passed = function() { 1411 return this.passedCount === this.totalCount; 1412 }; 1413 /** 1414 * Base class for pretty printing for expectation results. 1415 */ 1416 jasmine.PrettyPrinter = function() { 1417 this.ppNestLevel_ = 0; 1418 }; 1419 1420 /** 1421 * Formats a value in a nice, human-readable string. 1422 * 1423 * @param value 1424 * @returns {String} 1425 */ 1426 jasmine.PrettyPrinter.prototype.format = function(value) { 1427 if (this.ppNestLevel_ > 40) { 1428 // return '(jasmine.pp nested too deeply!)'; 1429 throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1430 } 1431 1432 this.ppNestLevel_++; 1433 try { 1434 if (value === undefined) { 1435 this.emitScalar('undefined'); 1436 } else if (value === null) { 1437 this.emitScalar('null'); 1438 } else if (value.navigator && value.frames && value.setTimeout) { 1439 this.emitScalar('<window>'); 1440 } else if (value instanceof jasmine.Matchers.Any) { 1441 this.emitScalar(value.toString()); 1442 } else if (typeof value === 'string') { 1443 this.emitScalar("'" + value + "'"); 1444 } else if (typeof value === 'function') { 1445 this.emitScalar('Function'); 1446 } else if (typeof value.nodeType === 'number') { 1447 this.emitScalar('HTMLNode'); 1448 } else if (value instanceof Date) { 1449 this.emitScalar('Date(' + value + ')'); 1450 } else if (value.__Jasmine_been_here_before__) { 1451 this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); 1452 } else if (jasmine.isArray_(value) || typeof value == 'object') { 1453 value.__Jasmine_been_here_before__ = true; 1454 if (jasmine.isArray_(value)) { 1455 this.emitArray(value); 1456 } else { 1457 this.emitObject(value); 1458 } 1459 delete value.__Jasmine_been_here_before__; 1460 } else { 1461 this.emitScalar(value.toString()); 1462 } 1463 } finally { 1464 this.ppNestLevel_--; 1465 } 1466 }; 1467 1468 jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1469 for (var property in obj) { 1470 if (property == '__Jasmine_been_here_before__') continue; 1471 fn(property, obj.__lookupGetter__(property) != null); 1472 } 1473 }; 1474 1475 jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1476 jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1477 jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1478 1479 jasmine.StringPrettyPrinter = function() { 1480 jasmine.PrettyPrinter.call(this); 1481 1482 this.string = ''; 1483 }; 1484 jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1485 1486 jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1487 this.append(value); 1488 }; 1489 1490 jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1491 this.append('[ '); 1492 for (var i = 0; i < array.length; i++) { 1493 if (i > 0) { 1494 this.append(', '); 1495 } 1496 this.format(array[i]); 1497 } 1498 this.append(' ]'); 1499 }; 1500 1501 jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1502 var self = this; 1503 this.append('{ '); 1504 var first = true; 1505 1506 this.iterateObject(obj, function(property, isGetter) { 1507 if (first) { 1508 first = false; 1509 } else { 1510 self.append(', '); 1511 } 1512 1513 self.append(property); 1514 self.append(' : '); 1515 if (isGetter) { 1516 self.append('<getter>'); 1517 } else { 1518 self.format(obj[property]); 1519 } 1520 }); 1521 1522 this.append(' }'); 1523 }; 1524 1525 jasmine.StringPrettyPrinter.prototype.append = function(value) { 1526 this.string += value; 1527 }; 1528 /** 1529 * QueuedFunction is how ActionCollections' actions are implemented 1530 * 1531 * @constructor 1532 * @param {jasmine.Env} env 1533 * @param {Function} func 1534 * @param {Number} timeout 1535 * @param {Function} latchFunction 1536 * @param {jasmine.Spec} spec 1537 */ 1538 jasmine.QueuedFunction = function(env, func, timeout, latchFunction, spec) { 1539 this.env = env; 1540 this.func = func; 1541 this.timeout = timeout; 1542 this.latchFunction = latchFunction; 1543 this.spec = spec; 1544 1545 this.totalTimeSpentWaitingForLatch = 0; 1546 this.latchTimeoutIncrement = 100; 1547 }; 1548 1549 jasmine.QueuedFunction.prototype.next = function() { 1550 this.spec.finish(); // default value is to be done after one function 1551 }; 1552 1553 jasmine.QueuedFunction.prototype.safeExecute = function() { 1554 this.env.reporter.log('>> Jasmine Running ' + this.spec.suite.description + ' ' + this.spec.description + '...'); 1555 1556 try { 1557 this.func.apply(this.spec); 1558 } catch (e) { 1559 this.fail(e); 1560 } 1561 }; 1562 1563 jasmine.QueuedFunction.prototype.execute = function() { 1564 var self = this; 1565 var executeNow = function() { 1566 self.safeExecute(); 1567 self.next(); 1568 }; 1569 1570 var executeLater = function() { 1571 self.env.setTimeout(executeNow, self.timeout); 1572 }; 1573 1574 var executeNowOrLater = function() { 1575 var latchFunctionResult; 1576 1577 try { 1578 latchFunctionResult = self.latchFunction.apply(self.spec); 1579 } catch (e) { 1580 self.fail(e); 1581 self.next(); 1582 return; 1583 } 1584 1585 if (latchFunctionResult) { 1586 executeNow(); 1587 } else if (self.totalTimeSpentWaitingForLatch >= self.timeout) { 1588 var message = 'timed out after ' + self.timeout + ' msec waiting for ' + (self.latchFunction.description || 'something to happen'); 1589 self.fail({ 1590 name: 'timeout', 1591 message: message 1592 }); 1593 self.next(); 1594 } else { 1595 self.totalTimeSpentWaitingForLatch += self.latchTimeoutIncrement; 1596 self.env.setTimeout(executeNowOrLater, self.latchTimeoutIncrement); 1597 } 1598 }; 1599 1600 if (this.latchFunction !== undefined) { 1601 executeNowOrLater(); 1602 } else if (this.timeout > 0) { 1603 executeLater(); 1604 } else { 1605 executeNow(); 1606 } 1607 }; 1608 1609 jasmine.QueuedFunction.prototype.fail = function(e) { 1610 this.spec.results.addResult(new jasmine.ExpectationResult(false, jasmine.util.formatException(e), null)); 1611 }; 1612 /* JasmineReporters.reporter 1613 * Base object that will get called whenever a Spec, Suite, or Runner is done. It is up to 1614 * descendants of this object to do something with the results (see json_reporter.js) 1615 */ 1616 jasmine.Reporters = {}; 1617 1618 jasmine.Reporters.reporter = function(callbacks) { 1619 var that = { 1620 callbacks: callbacks || {}, 1621 1622 doCallback: function(callback, results) { 1623 if (callback) { 1624 callback(results); 1625 } 1626 }, 1627 1628 reportRunnerResults: function(runner) { 1629 that.doCallback(that.callbacks.runnerCallback, runner); 1630 }, 1631 reportSuiteResults: function(suite) { 1632 that.doCallback(that.callbacks.suiteCallback, suite); 1633 }, 1634 reportSpecResults: function(spec) { 1635 that.doCallback(that.callbacks.specCallback, spec); 1636 }, 1637 log: function (str) { 1638 if (console && console.log) console.log(str); 1639 } 1640 }; 1641 1642 return that; 1643 }; 1644 1645 /** 1646 * Runner 1647 * 1648 * @constructor 1649 * @param {jasmine.Env} env 1650 */ 1651 jasmine.Runner = function(env) { 1652 jasmine.ActionCollection.call(this, env); 1653 1654 this.suites = this.actions; 1655 }; 1656 jasmine.util.inherit(jasmine.Runner, jasmine.ActionCollection); 1657 1658 jasmine.Runner.prototype.execute = function() { 1659 if (this.env.reporter.reportRunnerStarting) { 1660 this.env.reporter.reportRunnerStarting(this); 1661 } 1662 jasmine.ActionCollection.prototype.execute.call(this); 1663 }; 1664 1665 jasmine.Runner.prototype.finishCallback = function() { 1666 this.env.reporter.reportRunnerResults(this); 1667 }; 1668 1669 jasmine.Runner.prototype.getResults = function() { 1670 var results = new jasmine.NestedResults(); 1671 for (var i = 0; i < this.suites.length; i++) { 1672 results.rollupCounts(this.suites[i].getResults()); 1673 } 1674 return results; 1675 }; 1676 /** 1677 * Internal representation of a Jasmine specification, or test. 1678 * 1679 * @constructor 1680 * @param {jasmine.Env} env 1681 * @param {jasmine.Suite} suite 1682 * @param {String} description 1683 */ 1684 jasmine.Spec = function(env, suite, description) { 1685 this.id = env.nextSpecId_++; 1686 this.env = env; 1687 this.suite = suite; 1688 this.description = description; 1689 this.queue = []; 1690 this.currentTimeout = 0; 1691 this.currentLatchFunction = undefined; 1692 this.finished = false; 1693 this.afterCallbacks = []; 1694 this.spies_ = []; 1695 1696 this.results = new jasmine.NestedResults(); 1697 this.results.description = description; 1698 this.runs = this.addToQueue; 1699 this.matchersClass = null; 1700 }; 1701 1702 jasmine.Spec.prototype.getFullName = function() { 1703 return this.suite.getFullName() + ' ' + this.description + '.'; 1704 }; 1705 1706 jasmine.Spec.prototype.getResults = function() { 1707 return this.results; 1708 }; 1709 1710 jasmine.Spec.prototype.addToQueue = function(func) { 1711 var queuedFunction = new jasmine.QueuedFunction(this.env, func, this.currentTimeout, this.currentLatchFunction, this); 1712 this.queue.push(queuedFunction); 1713 1714 if (this.queue.length > 1) { 1715 var previousQueuedFunction = this.queue[this.queue.length - 2]; 1716 previousQueuedFunction.next = function() { 1717 queuedFunction.execute(); 1718 }; 1719 } 1720 1721 this.resetTimeout(); 1722 return this; 1723 }; 1724 1725 /** 1726 * @private 1727 * @deprecated 1728 */ 1729 jasmine.Spec.prototype.expects_that = function(actual) { 1730 return this.expect(actual); 1731 }; 1732 1733 /** 1734 * @private 1735 */ 1736 jasmine.Spec.prototype.expect = function(actual) { 1737 return new (this.getMatchersClass_())(this.env, actual, this.results); 1738 }; 1739 1740 /** 1741 * @private 1742 */ 1743 jasmine.Spec.prototype.waits = function(timeout) { 1744 this.currentTimeout = timeout; 1745 this.currentLatchFunction = undefined; 1746 return this; 1747 }; 1748 1749 /** 1750 * @private 1751 */ 1752 jasmine.Spec.prototype.waitsFor = function(timeout, latchFunction, message) { 1753 this.currentTimeout = timeout; 1754 this.currentLatchFunction = latchFunction; 1755 this.currentLatchFunction.description = message; 1756 return this; 1757 }; 1758 1759 jasmine.Spec.prototype.getMatchersClass_ = function() { 1760 return this.matchersClass || jasmine.Matchers; 1761 }; 1762 1763 jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 1764 var parent = this.getMatchersClass_(); 1765 var newMatchersClass = function() { 1766 parent.apply(this, arguments); 1767 }; 1768 jasmine.util.inherit(newMatchersClass, parent); 1769 for (var method in matchersPrototype) { 1770 newMatchersClass.prototype[method] = matchersPrototype[method]; 1771 } 1772 this.matchersClass = newMatchersClass; 1773 }; 1774 1775 jasmine.Spec.prototype.resetTimeout = function() { 1776 this.currentTimeout = 0; 1777 this.currentLatchFunction = undefined; 1778 }; 1779 1780 jasmine.Spec.prototype.finishCallback = function() { 1781 this.env.reporter.reportSpecResults(this); 1782 }; 1783 1784 jasmine.Spec.prototype.finish = function() { 1785 this.safeExecuteAfters(); 1786 1787 this.removeAllSpies(); 1788 this.finishCallback(); 1789 this.finished = true; 1790 }; 1791 1792 jasmine.Spec.prototype.after = function(doAfter) { 1793 this.afterCallbacks.unshift(doAfter); 1794 }; 1795 1796 jasmine.Spec.prototype.execute = function() { 1797 if (!this.env.specFilter(this)) { 1798 this.results.skipped = true; 1799 this.finishCallback(); 1800 this.finished = true; 1801 return; 1802 } 1803 1804 this.env.currentSpec = this; 1805 this.env.currentlyRunningTests = true; 1806 1807 this.safeExecuteBefores(); 1808 1809 if (this.queue[0]) { 1810 this.queue[0].execute(); 1811 } else { 1812 this.finish(); 1813 } 1814 this.env.currentlyRunningTests = false; 1815 }; 1816 1817 jasmine.Spec.prototype.safeExecuteBefores = function() { 1818 var befores = []; 1819 for (var suite = this.suite; suite; suite = suite.parentSuite) { 1820 if (suite.beforeEachFunction) befores.push(suite.beforeEachFunction); 1821 } 1822 1823 while (befores.length) { 1824 this.safeExecuteBeforeOrAfter(befores.pop()); 1825 } 1826 }; 1827 1828 jasmine.Spec.prototype.safeExecuteAfters = function() { 1829 for (var suite = this.suite; suite; suite = suite.parentSuite) { 1830 if (suite.afterEachFunction) this.safeExecuteBeforeOrAfter(suite.afterEachFunction); 1831 } 1832 }; 1833 1834 jasmine.Spec.prototype.safeExecuteBeforeOrAfter = function(func) { 1835 try { 1836 func.apply(this); 1837 } catch (e) { 1838 this.results.addResult(new jasmine.ExpectationResult(false, func.typeName + '() fail: ' + jasmine.util.formatException(e), null)); 1839 } 1840 }; 1841 1842 jasmine.Spec.prototype.explodes = function() { 1843 throw 'explodes function should not have been called'; 1844 }; 1845 1846 jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 1847 if (obj == undefined) { 1848 throw "spyOn could not find an object to spy upon for " + methodName + "()"; 1849 } 1850 1851 if (!ignoreMethodDoesntExist && obj[methodName] === undefined) { 1852 throw methodName + '() method does not exist'; 1853 } 1854 1855 if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 1856 throw new Error(methodName + ' has already been spied upon'); 1857 } 1858 1859 var spyObj = jasmine.createSpy(methodName); 1860 1861 this.spies_.push(spyObj); 1862 spyObj.baseObj = obj; 1863 spyObj.methodName = methodName; 1864 spyObj.originalValue = obj[methodName]; 1865 1866 obj[methodName] = spyObj; 1867 1868 return spyObj; 1869 }; 1870 1871 jasmine.Spec.prototype.removeAllSpies = function() { 1872 for (var i = 0; i < this.spies_.length; i++) { 1873 var spy = this.spies_[i]; 1874 spy.baseObj[spy.methodName] = spy.originalValue; 1875 } 1876 this.spies_ = []; 1877 }; 1878 1879 /** 1880 * Internal representation of a Jasmine suite. 1881 * 1882 * @constructor 1883 * @param {jasmine.Env} env 1884 * @param {String} description 1885 * @param {Function} specDefinitions 1886 * @param {jasmine.Suite} parentSuite 1887 */ 1888 jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 1889 jasmine.ActionCollection.call(this, env); 1890 1891 this.description = description; 1892 this.specs = this.actions; 1893 this.parentSuite = parentSuite; 1894 1895 this.beforeEachFunction = null; 1896 this.afterEachFunction = null; 1897 }; 1898 jasmine.util.inherit(jasmine.Suite, jasmine.ActionCollection); 1899 1900 jasmine.Suite.prototype.getFullName = function() { 1901 var fullName = this.description; 1902 for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 1903 fullName = parentSuite.description + ' ' + fullName; 1904 } 1905 return fullName; 1906 }; 1907 1908 jasmine.Suite.prototype.finishCallback = function() { 1909 this.env.reporter.reportSuiteResults(this); 1910 }; 1911 1912 jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 1913 beforeEachFunction.typeName = 'beforeEach'; 1914 this.beforeEachFunction = beforeEachFunction; 1915 }; 1916 1917 jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 1918 afterEachFunction.typeName = 'afterEach'; 1919 this.afterEachFunction = afterEachFunction; 1920 }; 1921 1922 jasmine.Suite.prototype.getResults = function() { 1923 var results = new jasmine.NestedResults(); 1924 for (var i = 0; i < this.specs.length; i++) { 1925 results.rollupCounts(this.specs[i].getResults()); 1926 } 1927 return results; 1928 }; 1929 1930