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