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