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