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": 10, 532 "build": 0, 533 "revision": 1256675463 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 604 this.reporter = new jasmine.MultiReporter(); 605 606 this.updateInterval = jasmine.UPDATE_INTERVAL 607 this.lastUpdate = 0; 608 this.specFilter = function() { 609 return true; 610 }; 611 612 this.nextSpecId_ = 0; 613 this.nextSuiteId_ = 0; 614 this.equalityTesters_ = []; 615 }; 616 617 618 jasmine.Env.prototype.setTimeout = jasmine.setTimeout; 619 jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; 620 jasmine.Env.prototype.setInterval = jasmine.setInterval; 621 jasmine.Env.prototype.clearInterval = jasmine.clearInterval; 622 623 /** 624 * @returns an object containing jasmine version build info, if set. 625 */ 626 jasmine.Env.prototype.version = function () { 627 if (jasmine.version_) { 628 return jasmine.version_; 629 } else { 630 throw new Error('Version not set'); 631 } 632 }; 633 634 /** 635 * @returns a sequential integer starting at 0 636 */ 637 jasmine.Env.prototype.nextSpecId = function () { 638 return this.nextSpecId_++; 639 }; 640 641 /** 642 * @returns a sequential integer starting at 0 643 */ 644 jasmine.Env.prototype.nextSuiteId = function () { 645 return this.nextSuiteId_++; 646 }; 647 648 /** 649 * Register a reporter to receive status updates from Jasmine. 650 * @param {jasmine.Reporter} reporter An object which will receive status updates. 651 */ 652 jasmine.Env.prototype.addReporter = function(reporter) { 653 this.reporter.addReporter(reporter); 654 }; 655 656 jasmine.Env.prototype.execute = function() { 657 this.currentRunner_.execute(); 658 }; 659 660 jasmine.Env.prototype.describe = function(description, specDefinitions) { 661 var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); 662 663 var parentSuite = this.currentSuite; 664 if (parentSuite) { 665 parentSuite.add(suite); 666 } else { 667 this.currentRunner_.add(suite); 668 } 669 670 this.currentSuite = suite; 671 672 specDefinitions.call(suite); 673 674 this.currentSuite = parentSuite; 675 676 return suite; 677 }; 678 679 jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { 680 if (this.currentSuite) { 681 this.currentSuite.beforeEach(beforeEachFunction); 682 } else { 683 this.currentRunner_.beforeEach(beforeEachFunction); 684 } 685 }; 686 687 jasmine.Env.prototype.currentRunner = function () { 688 return this.currentRunner_; 689 }; 690 691 jasmine.Env.prototype.afterEach = function(afterEachFunction) { 692 if (this.currentSuite) { 693 this.currentSuite.afterEach(afterEachFunction); 694 } else { 695 this.currentRunner_.afterEach(afterEachFunction); 696 } 697 698 }; 699 700 jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { 701 return { 702 execute: function() { 703 } 704 }; 705 }; 706 707 jasmine.Env.prototype.it = function(description, func) { 708 var spec = new jasmine.Spec(this, this.currentSuite, description); 709 this.currentSuite.add(spec); 710 this.currentSpec = spec; 711 712 if (func) { 713 spec.runs(func); 714 } 715 716 return spec; 717 }; 718 719 jasmine.Env.prototype.xit = function(desc, func) { 720 return { 721 id: this.nextSpecId(), 722 runs: function() { 723 } 724 }; 725 }; 726 727 jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { 728 if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { 729 return true; 730 } 731 732 a.__Jasmine_been_here_before__ = b; 733 b.__Jasmine_been_here_before__ = a; 734 735 var hasKey = function(obj, keyName) { 736 return obj != null && obj[keyName] !== undefined; 737 }; 738 739 for (var property in b) { 740 if (!hasKey(a, property) && hasKey(b, property)) { 741 mismatchKeys.push("expected has key '" + property + "', but missing from <b>actual</b>."); 742 } 743 } 744 for (property in a) { 745 if (!hasKey(b, property) && hasKey(a, property)) { 746 mismatchKeys.push("<b>expected</b> missing key '" + property + "', but present in actual."); 747 } 748 } 749 for (property in b) { 750 if (property == '__Jasmine_been_here_before__') continue; 751 if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { 752 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 />"); 753 } 754 } 755 756 if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { 757 mismatchValues.push("arrays were not the same length"); 758 } 759 760 delete a.__Jasmine_been_here_before__; 761 delete b.__Jasmine_been_here_before__; 762 return (mismatchKeys.length == 0 && mismatchValues.length == 0); 763 }; 764 765 jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { 766 mismatchKeys = mismatchKeys || []; 767 mismatchValues = mismatchValues || []; 768 769 if (a === b) return true; 770 771 if (a === undefined || a === null || b === undefined || b === null) { 772 return (a == undefined && b == undefined); 773 } 774 775 if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { 776 return a === b; 777 } 778 779 if (a instanceof Date && b instanceof Date) { 780 return a.getTime() == b.getTime(); 781 } 782 783 if (a instanceof jasmine.Matchers.Any) { 784 return a.matches(b); 785 } 786 787 if (b instanceof jasmine.Matchers.Any) { 788 return b.matches(a); 789 } 790 791 if (typeof a === "object" && typeof b === "object") { 792 return this.compareObjects_(a, b, mismatchKeys, mismatchValues); 793 } 794 795 for (var i = 0; i < this.equalityTesters_.length; i++) { 796 var equalityTester = this.equalityTesters_[i]; 797 var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); 798 if (result !== undefined) return result; 799 } 800 801 //Straight check 802 return (a === b); 803 }; 804 805 jasmine.Env.prototype.contains_ = function(haystack, needle) { 806 if (jasmine.isArray_(haystack)) { 807 for (var i = 0; i < haystack.length; i++) { 808 if (this.equals_(haystack[i], needle)) return true; 809 } 810 return false; 811 } 812 return haystack.indexOf(needle) >= 0; 813 }; 814 815 jasmine.Env.prototype.addEqualityTester = function(equalityTester) { 816 this.equalityTesters_.push(equalityTester); 817 }; 818 /** No-op base class for Jasmine reporters. 819 * 820 * @constructor 821 */ 822 jasmine.Reporter = function() { 823 }; 824 825 //noinspection JSUnusedLocalSymbols 826 jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { 827 }; 828 829 //noinspection JSUnusedLocalSymbols 830 jasmine.Reporter.prototype.reportRunnerResults = function(runner) { 831 }; 832 833 //noinspection JSUnusedLocalSymbols 834 jasmine.Reporter.prototype.reportSuiteResults = function(suite) { 835 }; 836 837 //noinspection JSUnusedLocalSymbols 838 jasmine.Reporter.prototype.reportSpecResults = function(spec) { 839 }; 840 841 //noinspection JSUnusedLocalSymbols 842 jasmine.Reporter.prototype.log = function(str) { 843 }; 844 845 /** 846 * Blocks are functions with executable code that make up a spec. 847 * 848 * @constructor 849 * @param {jasmine.Env} env 850 * @param {Function} func 851 * @param {jasmine.Spec} spec 852 */ 853 jasmine.Block = function(env, func, spec) { 854 this.env = env; 855 this.func = func; 856 this.spec = spec; 857 }; 858 859 jasmine.Block.prototype.execute = function(onComplete) { 860 try { 861 this.func.apply(this.spec); 862 } catch (e) { 863 this.spec.fail(e); 864 } 865 onComplete(); 866 }; 867 /** JavaScript API reporter. 868 * 869 * @constructor 870 */ 871 jasmine.JsApiReporter = function() { 872 this.started = false; 873 this.finished = false; 874 this.suites_ = []; 875 this.results_ = {}; 876 }; 877 878 jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { 879 this.started = true; 880 var suites = runner.suites(); 881 for (var i = 0; i < suites.length; i++) { 882 var suite = suites[i]; 883 this.suites_.push(this.summarize_(suite)); 884 } 885 }; 886 887 jasmine.JsApiReporter.prototype.suites = function() { 888 return this.suites_; 889 }; 890 891 jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { 892 var isSuite = suiteOrSpec instanceof jasmine.Suite 893 var summary = { 894 id: suiteOrSpec.id, 895 name: suiteOrSpec.description, 896 type: isSuite ? 'suite' : 'spec', 897 children: [] 898 }; 899 if (isSuite) { 900 var specs = suiteOrSpec.specs(); 901 for (var i = 0; i < specs.length; i++) { 902 summary.children.push(this.summarize_(specs[i])); 903 } 904 } 905 return summary; 906 }; 907 908 jasmine.JsApiReporter.prototype.results = function() { 909 return this.results_; 910 }; 911 912 jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { 913 return this.results_[specId]; 914 }; 915 916 //noinspection JSUnusedLocalSymbols 917 jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { 918 this.finished = true; 919 }; 920 921 //noinspection JSUnusedLocalSymbols 922 jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { 923 }; 924 925 //noinspection JSUnusedLocalSymbols 926 jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { 927 this.results_[spec.id] = { 928 messages: spec.results().getItems(), 929 result: spec.results().failedCount > 0 ? "failed" : "passed" 930 }; 931 }; 932 933 //noinspection JSUnusedLocalSymbols 934 jasmine.JsApiReporter.prototype.log = function(str) { 935 }; 936 937 jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ 938 var results = {}; 939 for (var i = 0; i < specIds.length; i++) { 940 var specId = specIds[i]; 941 results[specId] = this.summarizeResult_(this.results_[specId]); 942 } 943 return results; 944 }; 945 946 jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ 947 var summaryMessages = []; 948 for (var messageIndex in result.messages) { 949 var resultMessage = result.messages[messageIndex]; 950 summaryMessages.push({ 951 text: resultMessage.text, 952 passed: resultMessage.passed ? resultMessage.passed() : true, 953 type: resultMessage.type, 954 message: resultMessage.message, 955 trace: { 956 stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : undefined 957 } 958 }); 959 }; 960 961 var summaryResult = { 962 result : result.result, 963 messages : summaryMessages 964 }; 965 966 return summaryResult; 967 }; 968 969 /** 970 * @param env, actual, results 971 */ 972 jasmine.Matchers = function(env, actual, results) { 973 this.env = env; 974 this.actual = actual; 975 this.passing_message = 'Passed.'; 976 this.results_ = results || new jasmine.NestedResults(); 977 }; 978 979 jasmine.Matchers.pp = function(str) { 980 return jasmine.util.htmlEscape(jasmine.pp(str)); 981 }; 982 983 jasmine.Matchers.prototype.results = function() { 984 return this.results_; 985 }; 986 987 jasmine.Matchers.prototype.report = function(result, failing_message, details) { 988 this.results_.addResult(new jasmine.ExpectationResult(result, result ? this.passing_message : failing_message, details)); 989 return result; 990 }; 991 992 /** 993 * Matcher that compares the actual to the expected using ===. 994 * 995 * @param expected 996 */ 997 jasmine.Matchers.prototype.toBe = function(expected) { 998 return this.report(this.actual === expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected) 999 + '<br /><br />to be the same object as<br /><br />' + jasmine.Matchers.pp(this.actual) 1000 + '<br />'); 1001 }; 1002 1003 /** 1004 * Matcher that compares the actual to the expected using !== 1005 * @param expected 1006 */ 1007 jasmine.Matchers.prototype.toNotBe = function(expected) { 1008 return this.report(this.actual !== expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected) 1009 + '<br /><br />to be a different object from actual, but they were the same.'); 1010 }; 1011 1012 /** 1013 * Matcher that compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. 1014 * 1015 * @param expected 1016 */ 1017 jasmine.Matchers.prototype.toEqual = function(expected) { 1018 var mismatchKeys = []; 1019 var mismatchValues = []; 1020 1021 var formatMismatches = function(name, array) { 1022 if (array.length == 0) return ''; 1023 var errorOutput = '<br /><br />Different ' + name + ':<br />'; 1024 for (var i = 0; i < array.length; i++) { 1025 errorOutput += array[i] + '<br />'; 1026 } 1027 return errorOutput; 1028 }; 1029 1030 return this.report(this.env.equals_(this.actual, expected, mismatchKeys, mismatchValues), 1031 'Expected<br /><br />' + jasmine.Matchers.pp(expected) 1032 + '<br /><br />but got<br /><br />' + jasmine.Matchers.pp(this.actual) 1033 + '<br />' 1034 + formatMismatches('Keys', mismatchKeys) 1035 + formatMismatches('Values', mismatchValues), { 1036 matcherName: 'toEqual', expected: expected, actual: this.actual 1037 }); 1038 }; 1039 1040 /** 1041 * Matcher that compares the actual to the expected using the ! of jasmine.Matchers.toEqual 1042 * @param expected 1043 */ 1044 jasmine.Matchers.prototype.toNotEqual = function(expected) { 1045 return this.report(!this.env.equals_(this.actual, expected), 1046 'Expected ' + jasmine.Matchers.pp(expected) + ' to not equal ' + jasmine.Matchers.pp(this.actual) + ', but it does.'); 1047 }; 1048 1049 /** 1050 * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes 1051 * a pattern or a String. 1052 * 1053 * @param reg_exp 1054 */ 1055 jasmine.Matchers.prototype.toMatch = function(reg_exp) { 1056 return this.report((new RegExp(reg_exp).test(this.actual)), 1057 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to match ' + reg_exp + '.'); 1058 }; 1059 1060 /** 1061 * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch 1062 * @param reg_exp 1063 */ 1064 jasmine.Matchers.prototype.toNotMatch = function(reg_exp) { 1065 return this.report((!new RegExp(reg_exp).test(this.actual)), 1066 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to not match ' + reg_exp + '.'); 1067 }; 1068 1069 /** 1070 * Matcher that compares the acutal to undefined. 1071 */ 1072 jasmine.Matchers.prototype.toBeDefined = function() { 1073 return this.report((this.actual !== undefined), 1074 'Expected a value to be defined but it was undefined.'); 1075 }; 1076 1077 /** 1078 * Matcher that compares the actual to null. 1079 * 1080 */ 1081 jasmine.Matchers.prototype.toBeNull = function() { 1082 return this.report((this.actual === null), 1083 'Expected a value to be null but it was ' + jasmine.Matchers.pp(this.actual) + '.'); 1084 }; 1085 1086 /** 1087 * Matcher that boolean not-nots the actual. 1088 */ 1089 jasmine.Matchers.prototype.toBeTruthy = function() { 1090 return this.report(!!this.actual, 1091 'Expected a value to be truthy but it was ' + jasmine.Matchers.pp(this.actual) + '.'); 1092 }; 1093 1094 /** 1095 * Matcher that boolean nots the actual. 1096 */ 1097 jasmine.Matchers.prototype.toBeFalsy = function() { 1098 return this.report(!this.actual, 1099 'Expected a value to be falsy but it was ' + jasmine.Matchers.pp(this.actual) + '.'); 1100 }; 1101 1102 /** 1103 * Matcher that checks to see if the acutal, a Jasmine spy, was called. 1104 */ 1105 jasmine.Matchers.prototype.wasCalled = function() { 1106 if (!this.actual || !this.actual.isSpy) { 1107 return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); 1108 } 1109 if (arguments.length > 0) { 1110 return this.report(false, 'wasCalled matcher does not take arguments'); 1111 } 1112 return this.report((this.actual.wasCalled), 1113 'Expected spy "' + this.actual.identity + '" to have been called, but it was not.'); 1114 }; 1115 1116 /** 1117 * Matcher that checks to see if the acutal, a Jasmine spy, was not called. 1118 */ 1119 jasmine.Matchers.prototype.wasNotCalled = function() { 1120 if (!this.actual || !this.actual.isSpy) { 1121 return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.'); 1122 } 1123 return this.report((!this.actual.wasCalled), 1124 'Expected spy "' + this.actual.identity + '" to not have been called, but it was.'); 1125 }; 1126 1127 /** 1128 * Matcher that checks to see if the acutal, a Jasmine spy, was called with a set of parameters. 1129 * 1130 * @example 1131 * 1132 */ 1133 jasmine.Matchers.prototype.wasCalledWith = function() { 1134 if (!this.actual || !this.actual.isSpy) { 1135 return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.', { 1136 matcherName: 'wasCalledWith' 1137 }); 1138 } 1139 1140 var args = jasmine.util.argsToArray(arguments); 1141 1142 return this.report(this.env.contains_(this.actual.argsForCall, args), 1143 'Expected ' + jasmine.Matchers.pp(this.actual.argsForCall) + ' to contain ' + jasmine.Matchers.pp(args) + ', but it does not.', { 1144 matcherName: 'wasCalledWith', expected: args, actual: this.actual.argsForCall 1145 }); 1146 }; 1147 1148 /** 1149 * Matcher that checks that the expected item is an element in the actual Array. 1150 * 1151 * @param {Object} item 1152 */ 1153 jasmine.Matchers.prototype.toContain = function(item) { 1154 return this.report(this.env.contains_(this.actual, item), 1155 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to contain ' + jasmine.Matchers.pp(item) + ', but it does not.', { 1156 matcherName: 'toContain', expected: item, actual: this.actual 1157 }); 1158 }; 1159 1160 /** 1161 * Matcher that checks that the expected item is NOT an element in the actual Array. 1162 * 1163 * @param {Object} item 1164 */ 1165 jasmine.Matchers.prototype.toNotContain = function(item) { 1166 return this.report(!this.env.contains_(this.actual, item), 1167 'Expected ' + jasmine.Matchers.pp(this.actual) + ' not to contain ' + jasmine.Matchers.pp(item) + ', but it does.'); 1168 }; 1169 1170 jasmine.Matchers.prototype.toBeLessThan = function(expected) { 1171 return this.report(this.actual < expected, 1172 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be less than ' + jasmine.Matchers.pp(expected) + ', but it was not.'); 1173 }; 1174 1175 jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { 1176 return this.report(this.actual > expected, 1177 'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be greater than ' + jasmine.Matchers.pp(expected) + ', but it was not.'); 1178 }; 1179 1180 /** 1181 * Matcher that checks that the expected exception was thrown by the actual. 1182 * 1183 * @param {String} expectedException 1184 */ 1185 jasmine.Matchers.prototype.toThrow = function(expectedException) { 1186 var exception = null; 1187 try { 1188 this.actual(); 1189 } catch (e) { 1190 exception = e; 1191 } 1192 if (expectedException !== undefined) { 1193 if (exception == null) { 1194 return this.report(false, "Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it did not."); 1195 } 1196 return this.report( 1197 this.env.equals_( 1198 exception.message || exception, 1199 expectedException.message || expectedException), 1200 "Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it threw " + jasmine.Matchers.pp(exception) + "."); 1201 } else { 1202 return this.report(exception != null, "Expected function to throw an exception, but it did not."); 1203 } 1204 }; 1205 1206 jasmine.Matchers.Any = function(expectedClass) { 1207 this.expectedClass = expectedClass; 1208 }; 1209 1210 jasmine.Matchers.Any.prototype.matches = function(other) { 1211 if (this.expectedClass == String) { 1212 return typeof other == 'string' || other instanceof String; 1213 } 1214 1215 if (this.expectedClass == Number) { 1216 return typeof other == 'number' || other instanceof Number; 1217 } 1218 1219 if (this.expectedClass == Function) { 1220 return typeof other == 'function' || other instanceof Function; 1221 } 1222 1223 if (this.expectedClass == Object) { 1224 return typeof other == 'object'; 1225 } 1226 1227 return other instanceof this.expectedClass; 1228 }; 1229 1230 jasmine.Matchers.Any.prototype.toString = function() { 1231 return '<jasmine.any(' + this.expectedClass + ')>'; 1232 }; 1233 1234 /** 1235 * @constructor 1236 */ 1237 jasmine.MultiReporter = function() { 1238 this.subReporters_ = []; 1239 }; 1240 jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); 1241 1242 jasmine.MultiReporter.prototype.addReporter = function(reporter) { 1243 this.subReporters_.push(reporter); 1244 }; 1245 1246 (function() { 1247 var functionNames = ["reportRunnerStarting", "reportRunnerResults", "reportSuiteResults", "reportSpecResults", "log"]; 1248 for (var i = 0; i < functionNames.length; i++) { 1249 var functionName = functionNames[i]; 1250 jasmine.MultiReporter.prototype[functionName] = (function(functionName) { 1251 return function() { 1252 for (var j = 0; j < this.subReporters_.length; j++) { 1253 var subReporter = this.subReporters_[j]; 1254 if (subReporter[functionName]) { 1255 subReporter[functionName].apply(subReporter, arguments); 1256 } 1257 } 1258 }; 1259 })(functionName); 1260 } 1261 })(); 1262 /** 1263 * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults 1264 * 1265 * @constructor 1266 */ 1267 jasmine.NestedResults = function() { 1268 /** 1269 * The total count of results 1270 */ 1271 this.totalCount = 0; 1272 /** 1273 * Number of passed results 1274 */ 1275 this.passedCount = 0; 1276 /** 1277 * Number of failed results 1278 */ 1279 this.failedCount = 0; 1280 /** 1281 * Was this suite/spec skipped? 1282 */ 1283 this.skipped = false; 1284 /** 1285 * @ignore 1286 */ 1287 this.items_ = []; 1288 }; 1289 1290 /** 1291 * Roll up the result counts. 1292 * 1293 * @param result 1294 */ 1295 jasmine.NestedResults.prototype.rollupCounts = function(result) { 1296 this.totalCount += result.totalCount; 1297 this.passedCount += result.passedCount; 1298 this.failedCount += result.failedCount; 1299 }; 1300 1301 /** 1302 * Tracks a result's message. 1303 * @param message 1304 */ 1305 jasmine.NestedResults.prototype.log = function(message) { 1306 this.items_.push(new jasmine.MessageResult(message)); 1307 }; 1308 1309 /** 1310 * Getter for the results: message & results. 1311 */ 1312 jasmine.NestedResults.prototype.getItems = function() { 1313 return this.items_; 1314 }; 1315 1316 /** 1317 * Adds a result, tracking counts (total, passed, & failed) 1318 * @param {jasmine.ExpectationResult|jasmine.NestedResults} result 1319 */ 1320 jasmine.NestedResults.prototype.addResult = function(result) { 1321 if (result.type != 'MessageResult') { 1322 if (result.items_) { 1323 this.rollupCounts(result); 1324 } else { 1325 this.totalCount++; 1326 if (result.passed()) { 1327 this.passedCount++; 1328 } else { 1329 this.failedCount++; 1330 } 1331 } 1332 } 1333 this.items_.push(result); 1334 }; 1335 1336 /** 1337 * @returns {Boolean} True if <b>everything</b> below passed 1338 */ 1339 jasmine.NestedResults.prototype.passed = function() { 1340 return this.passedCount === this.totalCount; 1341 }; 1342 /** 1343 * Base class for pretty printing for expectation results. 1344 */ 1345 jasmine.PrettyPrinter = function() { 1346 this.ppNestLevel_ = 0; 1347 }; 1348 1349 /** 1350 * Formats a value in a nice, human-readable string. 1351 * 1352 * @param value 1353 * @returns {String} 1354 */ 1355 jasmine.PrettyPrinter.prototype.format = function(value) { 1356 if (this.ppNestLevel_ > 40) { 1357 // return '(jasmine.pp nested too deeply!)'; 1358 throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); 1359 } 1360 1361 this.ppNestLevel_++; 1362 try { 1363 if (value === undefined) { 1364 this.emitScalar('undefined'); 1365 } else if (value === null) { 1366 this.emitScalar('null'); 1367 } else if (value.navigator && value.frames && value.setTimeout) { 1368 this.emitScalar('<window>'); 1369 } else if (value instanceof jasmine.Matchers.Any) { 1370 this.emitScalar(value.toString()); 1371 } else if (typeof value === 'string') { 1372 this.emitString(value); 1373 } else if (typeof value === 'function') { 1374 this.emitScalar('Function'); 1375 } else if (typeof value.nodeType === 'number') { 1376 this.emitScalar('HTMLNode'); 1377 } else if (value instanceof Date) { 1378 this.emitScalar('Date(' + value + ')'); 1379 } else if (value.__Jasmine_been_here_before__) { 1380 this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); 1381 } else if (jasmine.isArray_(value) || typeof value == 'object') { 1382 value.__Jasmine_been_here_before__ = true; 1383 if (jasmine.isArray_(value)) { 1384 this.emitArray(value); 1385 } else { 1386 this.emitObject(value); 1387 } 1388 delete value.__Jasmine_been_here_before__; 1389 } else { 1390 this.emitScalar(value.toString()); 1391 } 1392 } finally { 1393 this.ppNestLevel_--; 1394 } 1395 }; 1396 1397 jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { 1398 for (var property in obj) { 1399 if (property == '__Jasmine_been_here_before__') continue; 1400 fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false); 1401 } 1402 }; 1403 1404 jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; 1405 jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; 1406 jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; 1407 jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; 1408 1409 jasmine.StringPrettyPrinter = function() { 1410 jasmine.PrettyPrinter.call(this); 1411 1412 this.string = ''; 1413 }; 1414 jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); 1415 1416 jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { 1417 this.append(value); 1418 }; 1419 1420 jasmine.StringPrettyPrinter.prototype.emitString = function(value) { 1421 this.append("'" + value + "'"); 1422 }; 1423 1424 jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { 1425 this.append('[ '); 1426 for (var i = 0; i < array.length; i++) { 1427 if (i > 0) { 1428 this.append(', '); 1429 } 1430 this.format(array[i]); 1431 } 1432 this.append(' ]'); 1433 }; 1434 1435 jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { 1436 var self = this; 1437 this.append('{ '); 1438 var first = true; 1439 1440 this.iterateObject(obj, function(property, isGetter) { 1441 if (first) { 1442 first = false; 1443 } else { 1444 self.append(', '); 1445 } 1446 1447 self.append(property); 1448 self.append(' : '); 1449 if (isGetter) { 1450 self.append('<getter>'); 1451 } else { 1452 self.format(obj[property]); 1453 } 1454 }); 1455 1456 this.append(' }'); 1457 }; 1458 1459 jasmine.StringPrettyPrinter.prototype.append = function(value) { 1460 this.string += value; 1461 }; 1462 jasmine.Queue = function(env) { 1463 this.env = env; 1464 this.blocks = []; 1465 this.running = false; 1466 this.index = 0; 1467 this.offset = 0; 1468 }; 1469 1470 jasmine.Queue.prototype.addBefore = function(block) { 1471 this.blocks.unshift(block); 1472 }; 1473 1474 jasmine.Queue.prototype.add = function(block) { 1475 this.blocks.push(block); 1476 }; 1477 1478 jasmine.Queue.prototype.insertNext = function(block) { 1479 this.blocks.splice((this.index + this.offset + 1), 0, block); 1480 this.offset++; 1481 }; 1482 1483 jasmine.Queue.prototype.start = function(onComplete) { 1484 this.running = true; 1485 this.onComplete = onComplete; 1486 this.next_(); 1487 }; 1488 1489 jasmine.Queue.prototype.isRunning = function() { 1490 return this.running; 1491 }; 1492 1493 jasmine.Queue.LOOP_DONT_RECURSE = true; 1494 1495 jasmine.Queue.prototype.next_ = function() { 1496 var self = this; 1497 var goAgain = true; 1498 1499 while (goAgain) { 1500 goAgain = false; 1501 1502 if (self.index < self.blocks.length) { 1503 var calledSynchronously = true; 1504 var completedSynchronously = false; 1505 1506 var onComplete = function () { 1507 if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { 1508 completedSynchronously = true; 1509 return; 1510 } 1511 1512 self.offset = 0; 1513 self.index++; 1514 1515 var now = new Date().getTime(); 1516 if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { 1517 self.env.lastUpdate = now; 1518 self.env.setTimeout(function() { 1519 self.next_(); 1520 }, 0); 1521 } else { 1522 if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { 1523 goAgain = true; 1524 } else { 1525 self.next_(); 1526 } 1527 } 1528 }; 1529 self.blocks[self.index].execute(onComplete); 1530 1531 calledSynchronously = false; 1532 if (completedSynchronously) { 1533 onComplete(); 1534 } 1535 1536 } else { 1537 self.running = false; 1538 if (self.onComplete) { 1539 self.onComplete(); 1540 } 1541 } 1542 } 1543 }; 1544 1545 jasmine.Queue.prototype.results = function() { 1546 var results = new jasmine.NestedResults(); 1547 for (var i = 0; i < this.blocks.length; i++) { 1548 if (this.blocks[i].results) { 1549 results.addResult(this.blocks[i].results()); 1550 } 1551 } 1552 return results; 1553 }; 1554 1555 1556 /* JasmineReporters.reporter 1557 * Base object that will get called whenever a Spec, Suite, or Runner is done. It is up to 1558 * descendants of this object to do something with the results (see json_reporter.js) 1559 */ 1560 jasmine.Reporters = {}; 1561 1562 jasmine.Reporters.reporter = function(callbacks) { 1563 var that = { 1564 callbacks: callbacks || {}, 1565 1566 doCallback: function(callback, results) { 1567 if (callback) { 1568 callback(results); 1569 } 1570 }, 1571 1572 reportRunnerResults: function(runner) { 1573 that.doCallback(that.callbacks.runnerCallback, runner); 1574 }, 1575 reportSuiteResults: function(suite) { 1576 that.doCallback(that.callbacks.suiteCallback, suite); 1577 }, 1578 reportSpecResults: function(spec) { 1579 that.doCallback(that.callbacks.specCallback, spec); 1580 }, 1581 log: function (str) { 1582 if (console && console.log) console.log(str); 1583 } 1584 }; 1585 1586 return that; 1587 }; 1588 1589 /** 1590 * Runner 1591 * 1592 * @constructor 1593 * @param {jasmine.Env} env 1594 */ 1595 jasmine.Runner = function(env) { 1596 var self = this; 1597 self.env = env; 1598 self.queue = new jasmine.Queue(env); 1599 self.before_ = []; 1600 self.after_ = []; 1601 self.suites_ = []; 1602 }; 1603 1604 jasmine.Runner.prototype.execute = function() { 1605 var self = this; 1606 if (self.env.reporter.reportRunnerStarting) { 1607 self.env.reporter.reportRunnerStarting(this); 1608 } 1609 self.queue.start(function () { 1610 self.finishCallback(); 1611 }); 1612 }; 1613 1614 jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { 1615 beforeEachFunction.typeName = 'beforeEach'; 1616 this.before_.push(beforeEachFunction); 1617 }; 1618 1619 jasmine.Runner.prototype.afterEach = function(afterEachFunction) { 1620 afterEachFunction.typeName = 'afterEach'; 1621 this.after_.push(afterEachFunction); 1622 }; 1623 1624 1625 jasmine.Runner.prototype.finishCallback = function() { 1626 this.env.reporter.reportRunnerResults(this); 1627 }; 1628 1629 jasmine.Runner.prototype.addSuite = function(suite) { 1630 this.suites_.push(suite); 1631 }; 1632 1633 jasmine.Runner.prototype.add = function(block) { 1634 if (block instanceof jasmine.Suite) { 1635 this.addSuite(block); 1636 } 1637 this.queue.add(block); 1638 }; 1639 1640 jasmine.Runner.prototype.specs = function () { 1641 var suites = this.suites(); 1642 var specs = []; 1643 for (var i = 0; i < suites.length; i++) { 1644 specs = specs.concat(suites[i].specs()); 1645 } 1646 return specs; 1647 }; 1648 1649 1650 jasmine.Runner.prototype.suites = function() { 1651 return this.suites_; 1652 }; 1653 1654 jasmine.Runner.prototype.results = function() { 1655 return this.queue.results(); 1656 }; 1657 /** 1658 * Internal representation of a Jasmine specification, or test. 1659 * 1660 * @constructor 1661 * @param {jasmine.Env} env 1662 * @param {jasmine.Suite} suite 1663 * @param {String} description 1664 */ 1665 jasmine.Spec = function(env, suite, description) { 1666 if (!env) { 1667 throw new Error('jasmine.Env() required'); 1668 } 1669 ; 1670 if (!suite) { 1671 throw new Error('jasmine.Suite() required'); 1672 } 1673 ; 1674 var spec = this; 1675 spec.id = env.nextSpecId ? env.nextSpecId() : null; 1676 spec.env = env; 1677 spec.suite = suite; 1678 spec.description = description; 1679 spec.queue = new jasmine.Queue(env); 1680 1681 spec.afterCallbacks = []; 1682 spec.spies_ = []; 1683 1684 spec.results_ = new jasmine.NestedResults(); 1685 spec.results_.description = description; 1686 spec.matchersClass = null; 1687 }; 1688 1689 jasmine.Spec.prototype.getFullName = function() { 1690 return this.suite.getFullName() + ' ' + this.description + '.'; 1691 }; 1692 1693 1694 jasmine.Spec.prototype.results = function() { 1695 return this.results_; 1696 }; 1697 1698 jasmine.Spec.prototype.log = function(message) { 1699 return this.results_.log(message); 1700 }; 1701 1702 jasmine.Spec.prototype.runs = function (func) { 1703 var block = new jasmine.Block(this.env, func, this); 1704 this.addToQueue(block); 1705 return this; 1706 }; 1707 1708 jasmine.Spec.prototype.addToQueue = function (block) { 1709 if (this.queue.isRunning()) { 1710 this.queue.insertNext(block); 1711 } else { 1712 this.queue.add(block); 1713 } 1714 }; 1715 1716 jasmine.Spec.prototype.expect = function(actual) { 1717 return new (this.getMatchersClass_())(this.env, actual, this.results_); 1718 }; 1719 1720 jasmine.Spec.prototype.waits = function(timeout) { 1721 var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); 1722 this.addToQueue(waitsFunc); 1723 return this; 1724 }; 1725 1726 jasmine.Spec.prototype.waitsFor = function(timeout, latchFunction, timeoutMessage) { 1727 var waitsForFunc = new jasmine.WaitsForBlock(this.env, timeout, latchFunction, timeoutMessage, this); 1728 this.addToQueue(waitsForFunc); 1729 return this; 1730 }; 1731 1732 jasmine.Spec.prototype.fail = function (e) { 1733 this.results_.addResult(new jasmine.ExpectationResult(false, e ? jasmine.util.formatException(e) : null, null)); 1734 }; 1735 1736 jasmine.Spec.prototype.getMatchersClass_ = function() { 1737 return this.matchersClass || jasmine.Matchers; 1738 }; 1739 1740 jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { 1741 var parent = this.getMatchersClass_(); 1742 var newMatchersClass = function() { 1743 parent.apply(this, arguments); 1744 }; 1745 jasmine.util.inherit(newMatchersClass, parent); 1746 for (var method in matchersPrototype) { 1747 newMatchersClass.prototype[method] = matchersPrototype[method]; 1748 } 1749 this.matchersClass = newMatchersClass; 1750 }; 1751 1752 jasmine.Spec.prototype.finishCallback = function() { 1753 this.env.reporter.reportSpecResults(this); 1754 }; 1755 1756 jasmine.Spec.prototype.finish = function(onComplete) { 1757 this.removeAllSpies(); 1758 this.finishCallback(); 1759 if (onComplete) { 1760 onComplete(); 1761 } 1762 }; 1763 1764 jasmine.Spec.prototype.after = function(doAfter, test) { 1765 1766 if (this.queue.isRunning()) { 1767 this.queue.add(new jasmine.Block(this.env, doAfter, this)); 1768 } else { 1769 this.afterCallbacks.unshift(doAfter); 1770 } 1771 }; 1772 1773 jasmine.Spec.prototype.execute = function(onComplete) { 1774 var spec = this; 1775 if (!spec.env.specFilter(spec)) { 1776 spec.results_.skipped = true; 1777 spec.finish(onComplete); 1778 return; 1779 } 1780 this.env.reporter.log('>> Jasmine Running ' + this.suite.description + ' ' + this.description + '...'); 1781 1782 spec.env.currentSpec = spec; 1783 1784 spec.addBeforesAndAftersToQueue(); 1785 1786 spec.queue.start(function () { 1787 spec.finish(onComplete); 1788 }); 1789 }; 1790 1791 jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { 1792 var runner = this.env.currentRunner(); 1793 for (var suite = this.suite; suite; suite = suite.parentSuite) { 1794 for (var i = 0; i < suite.before_.length; i++) { 1795 this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); 1796 } 1797 } 1798 for (var i = 0; i < runner.before_.length; i++) { 1799 this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); 1800 } 1801 for (i = 0; i < this.afterCallbacks.length; i++) { 1802 this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); 1803 } 1804 for (suite = this.suite; suite; suite = suite.parentSuite) { 1805 for (var i = 0; i < suite.after_.length; i++) { 1806 this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); 1807 } 1808 } 1809 for (var i = 0; i < runner.after_.length; i++) { 1810 this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); 1811 } 1812 }; 1813 1814 jasmine.Spec.prototype.explodes = function() { 1815 throw 'explodes function should not have been called'; 1816 }; 1817 1818 jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { 1819 if (obj == undefined) { 1820 throw "spyOn could not find an object to spy upon for " + methodName + "()"; 1821 } 1822 1823 if (!ignoreMethodDoesntExist && obj[methodName] === undefined) { 1824 throw methodName + '() method does not exist'; 1825 } 1826 1827 if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { 1828 throw new Error(methodName + ' has already been spied upon'); 1829 } 1830 1831 var spyObj = jasmine.createSpy(methodName); 1832 1833 this.spies_.push(spyObj); 1834 spyObj.baseObj = obj; 1835 spyObj.methodName = methodName; 1836 spyObj.originalValue = obj[methodName]; 1837 1838 obj[methodName] = spyObj; 1839 1840 return spyObj; 1841 }; 1842 1843 jasmine.Spec.prototype.removeAllSpies = function() { 1844 for (var i = 0; i < this.spies_.length; i++) { 1845 var spy = this.spies_[i]; 1846 spy.baseObj[spy.methodName] = spy.originalValue; 1847 } 1848 this.spies_ = []; 1849 }; 1850 1851 /** 1852 * Internal representation of a Jasmine suite. 1853 * 1854 * @constructor 1855 * @param {jasmine.Env} env 1856 * @param {String} description 1857 * @param {Function} specDefinitions 1858 * @param {jasmine.Suite} parentSuite 1859 */ 1860 jasmine.Suite = function(env, description, specDefinitions, parentSuite) { 1861 var self = this; 1862 self.id = env.nextSuiteId ? env.nextSuiteId() : null; 1863 self.description = description; 1864 self.queue = new jasmine.Queue(env); 1865 self.parentSuite = parentSuite; 1866 self.env = env; 1867 self.before_ = []; 1868 self.after_ = []; 1869 self.specs_ = []; 1870 }; 1871 1872 jasmine.Suite.prototype.getFullName = function() { 1873 var fullName = this.description; 1874 for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { 1875 fullName = parentSuite.description + ' ' + fullName; 1876 } 1877 return fullName; 1878 }; 1879 1880 jasmine.Suite.prototype.finish = function(onComplete) { 1881 this.env.reporter.reportSuiteResults(this); 1882 this.finished = true; 1883 if (typeof(onComplete) == 'function') { 1884 onComplete(); 1885 } 1886 }; 1887 1888 jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { 1889 beforeEachFunction.typeName = 'beforeEach'; 1890 this.before_.push(beforeEachFunction); 1891 }; 1892 1893 jasmine.Suite.prototype.afterEach = function(afterEachFunction) { 1894 afterEachFunction.typeName = 'afterEach'; 1895 this.after_.push(afterEachFunction); 1896 }; 1897 1898 jasmine.Suite.prototype.results = function() { 1899 return this.queue.results(); 1900 }; 1901 1902 jasmine.Suite.prototype.add = function(block) { 1903 if (block instanceof jasmine.Suite) { 1904 this.env.currentRunner().addSuite(block); 1905 } else { 1906 this.specs_.push(block); 1907 } 1908 this.queue.add(block); 1909 }; 1910 1911 jasmine.Suite.prototype.specs = function() { 1912 return this.specs_; 1913 }; 1914 1915 jasmine.Suite.prototype.execute = function(onComplete) { 1916 var self = this; 1917 this.queue.start(function () { 1918 self.finish(onComplete); 1919 }); 1920 }; 1921 jasmine.WaitsBlock = function(env, timeout, spec) { 1922 this.timeout = timeout; 1923 jasmine.Block.call(this, env, null, spec); 1924 }; 1925 1926 jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); 1927 1928 jasmine.WaitsBlock.prototype.execute = function (onComplete) { 1929 this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); 1930 this.env.setTimeout(function () { 1931 onComplete(); 1932 }, this.timeout); 1933 }; 1934 jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { 1935 this.timeout = timeout; 1936 this.latchFunction = latchFunction; 1937 this.message = message; 1938 this.totalTimeSpentWaitingForLatch = 0; 1939 jasmine.Block.call(this, env, null, spec); 1940 }; 1941 1942 jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); 1943 1944 jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 100; 1945 1946 jasmine.WaitsForBlock.prototype.execute = function (onComplete) { 1947 var self = this; 1948 self.env.reporter.log('>> Jasmine waiting for ' + (self.message || 'something to happen')); 1949 var latchFunctionResult; 1950 try { 1951 latchFunctionResult = self.latchFunction.apply(self.spec); 1952 } catch (e) { 1953 self.spec.fail(e); 1954 onComplete(); 1955 return; 1956 } 1957 1958 if (latchFunctionResult) { 1959 onComplete(); 1960 } else if (self.totalTimeSpentWaitingForLatch >= self.timeout) { 1961 var message = 'timed out after ' + self.timeout + ' msec waiting for ' + (self.message || 'something to happen'); 1962 self.spec.fail({ 1963 name: 'timeout', 1964 message: message 1965 }); 1966 self.spec._next(); 1967 } else { 1968 self.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; 1969 self.env.setTimeout(function () { self.execute(onComplete); }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); 1970 } 1971 }; 1972 // Mock setTimeout, clearTimeout 1973 // Contributed by Pivotal Computer Systems, www.pivotalsf.com 1974 1975 jasmine.FakeTimer = function() { 1976 this.reset(); 1977 1978 var self = this; 1979 self.setTimeout = function(funcToCall, millis) { 1980 self.timeoutsMade++; 1981 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); 1982 return self.timeoutsMade; 1983 }; 1984 1985 self.setInterval = function(funcToCall, millis) { 1986 self.timeoutsMade++; 1987 self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); 1988 return self.timeoutsMade; 1989 }; 1990 1991 self.clearTimeout = function(timeoutKey) { 1992 self.scheduledFunctions[timeoutKey] = undefined; 1993 }; 1994 1995 self.clearInterval = function(timeoutKey) { 1996 self.scheduledFunctions[timeoutKey] = undefined; 1997 }; 1998 1999 }; 2000 2001 jasmine.FakeTimer.prototype.reset = function() { 2002 this.timeoutsMade = 0; 2003 this.scheduledFunctions = {}; 2004 this.nowMillis = 0; 2005 }; 2006 2007 jasmine.FakeTimer.prototype.tick = function(millis) { 2008 var oldMillis = this.nowMillis; 2009 var newMillis = oldMillis + millis; 2010 this.runFunctionsWithinRange(oldMillis, newMillis); 2011 this.nowMillis = newMillis; 2012 }; 2013 2014 jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { 2015 var scheduledFunc; 2016 var funcsToRun = []; 2017 for (var timeoutKey in this.scheduledFunctions) { 2018 scheduledFunc = this.scheduledFunctions[timeoutKey]; 2019 if (scheduledFunc != undefined && 2020 scheduledFunc.runAtMillis >= oldMillis && 2021 scheduledFunc.runAtMillis <= nowMillis) { 2022 funcsToRun.push(scheduledFunc); 2023 this.scheduledFunctions[timeoutKey] = undefined; 2024 } 2025 } 2026 2027 if (funcsToRun.length > 0) { 2028 funcsToRun.sort(function(a, b) { 2029 return a.runAtMillis - b.runAtMillis; 2030 }); 2031 for (var i = 0; i < funcsToRun.length; ++i) { 2032 try { 2033 var funcToRun = funcsToRun[i]; 2034 this.nowMillis = funcToRun.runAtMillis; 2035 funcToRun.funcToCall(); 2036 if (funcToRun.recurring) { 2037 this.scheduleFunction(funcToRun.timeoutKey, 2038 funcToRun.funcToCall, 2039 funcToRun.millis, 2040 true); 2041 } 2042 } catch(e) { 2043 } 2044 } 2045 this.runFunctionsWithinRange(oldMillis, nowMillis); 2046 } 2047 }; 2048 2049 jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { 2050 this.scheduledFunctions[timeoutKey] = { 2051 runAtMillis: this.nowMillis + millis, 2052 funcToCall: funcToCall, 2053 recurring: recurring, 2054 timeoutKey: timeoutKey, 2055 millis: millis 2056 }; 2057 }; 2058 2059 2060 jasmine.Clock = { 2061 defaultFakeTimer: new jasmine.FakeTimer(), 2062 2063 reset: function() { 2064 jasmine.Clock.assertInstalled(); 2065 jasmine.Clock.defaultFakeTimer.reset(); 2066 }, 2067 2068 tick: function(millis) { 2069 jasmine.Clock.assertInstalled(); 2070 jasmine.Clock.defaultFakeTimer.tick(millis); 2071 }, 2072 2073 runFunctionsWithinRange: function(oldMillis, nowMillis) { 2074 jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); 2075 }, 2076 2077 scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { 2078 jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); 2079 }, 2080 2081 useMock: function() { 2082 var spec = jasmine.getEnv().currentSpec; 2083 spec.after(jasmine.Clock.uninstallMock); 2084 2085 jasmine.Clock.installMock(); 2086 }, 2087 2088 installMock: function() { 2089 jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; 2090 }, 2091 2092 uninstallMock: function() { 2093 jasmine.Clock.assertInstalled(); 2094 jasmine.Clock.installed = jasmine.Clock.real; 2095 }, 2096 2097 real: { 2098 setTimeout: window.setTimeout, 2099 clearTimeout: window.clearTimeout, 2100 setInterval: window.setInterval, 2101 clearInterval: window.clearInterval 2102 }, 2103 2104 assertInstalled: function() { 2105 if (jasmine.Clock.installed != jasmine.Clock.defaultFakeTimer) { 2106 throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); 2107 } 2108 }, 2109 2110 installed: null 2111 }; 2112 jasmine.Clock.installed = jasmine.Clock.real; 2113 2114 //else for IE support 2115 window.setTimeout = function(funcToCall, millis) { 2116 if (jasmine.Clock.installed.setTimeout.apply) { 2117 return jasmine.Clock.installed.setTimeout.apply(this, arguments); 2118 } else { 2119 return jasmine.Clock.installed.setTimeout(funcToCall, millis); 2120 } 2121 }; 2122 2123 window.setInterval = function(funcToCall, millis) { 2124 if (jasmine.Clock.installed.setInterval.apply) { 2125 return jasmine.Clock.installed.setInterval.apply(this, arguments); 2126 } else { 2127 return jasmine.Clock.installed.setInterval(funcToCall, millis); 2128 } 2129 }; 2130 2131 window.clearTimeout = function(timeoutKey) { 2132 if (jasmine.Clock.installed.clearTimeout.apply) { 2133 return jasmine.Clock.installed.clearTimeout.apply(this, arguments); 2134 } else { 2135 return jasmine.Clock.installed.clearTimeout(timeoutKey); 2136 } 2137 }; 2138 2139 window.clearInterval = function(timeoutKey) { 2140 if (jasmine.Clock.installed.clearTimeout.apply) { 2141 return jasmine.Clock.installed.clearInterval.apply(this, arguments); 2142 } else { 2143 return jasmine.Clock.installed.clearInterval(timeoutKey); 2144 } 2145 }; 2146 2147