1// script.aculo.us unittest.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 2 3// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) 4// (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com) 5// (c) 2005, 2006 Michael Schuerig (http://www.schuerig.de/michael/) 6// 7// script.aculo.us is freely distributable under the terms of an MIT-style license. 8// For details, see the script.aculo.us web site: http://script.aculo.us/ 9 10// experimental, Firefox-only 11 12Event.simulateMouse = function(element, eventName) { 13 var options = Object.extend({ 14 pointerX: 0, 15 pointerY: 0, 16 buttons: 0, 17 ctrlKey: false, 18 altKey: false, 19 shiftKey: false, 20 metaKey: false 21 }, arguments[2] || {}); 22 var oEvent = document.createEvent("MouseEvents"); 23 oEvent.initMouseEvent(eventName, true, true, document.defaultView, 24 options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 25 options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element)); 26 27 if(this.mark) Element.remove(this.mark); 28 this.mark = document.createElement('div'); 29 this.mark.appendChild(document.createTextNode(" ")); 30 document.body.appendChild(this.mark); 31 this.mark.style.position = 'absolute'; 32 this.mark.style.top = options.pointerY + "px"; 33 this.mark.style.left = options.pointerX + "px"; 34 this.mark.style.width = "5px"; 35 this.mark.style.height = "5px;"; 36 this.mark.style.borderTop = "1px solid red;" 37 this.mark.style.borderLeft = "1px solid red;" 38 39 if(this.step) 40 alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options)); 41 42 $(element).dispatchEvent(oEvent); 43}; 44 45// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2. 46// You need to downgrade to 1.0.4 for now to get this working 47// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much 48Event.simulateKey = function(element, eventName) { 49 var options = Object.extend({ 50 ctrlKey: false, 51 altKey: false, 52 shiftKey: false, 53 metaKey: false, 54 keyCode: 0, 55 charCode: 0 56 }, arguments[2] || {}); 57 58 var oEvent = document.createEvent("KeyEvents"); 59 oEvent.initKeyEvent(eventName, true, true, window, 60 options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 61 options.keyCode, options.charCode ); 62 $(element).dispatchEvent(oEvent); 63}; 64 65Event.simulateKeys = function(element, command) { 66 for(var i=0; i<command.length; i++) { 67 Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)}); 68 } 69}; 70 71var Test = {} 72Test.Unit = {}; 73 74// security exception workaround 75Test.Unit.inspect = Object.inspect; 76 77Test.Unit.Logger = Class.create(); 78Test.Unit.Logger.prototype = { 79 initialize: function(log) { 80 this.log = $(log); 81 if (this.log) { 82 this._createLogTable(); 83 } 84 }, 85 start: function(testName) { 86 if (!this.log) return; 87 this.testName = testName; 88 this.lastLogLine = document.createElement('tr'); 89 this.statusCell = document.createElement('td'); 90 this.nameCell = document.createElement('td'); 91 this.nameCell.className = "nameCell"; 92 this.nameCell.appendChild(document.createTextNode(testName)); 93 this.messageCell = document.createElement('td'); 94 this.lastLogLine.appendChild(this.statusCell); 95 this.lastLogLine.appendChild(this.nameCell); 96 this.lastLogLine.appendChild(this.messageCell); 97 this.loglines.appendChild(this.lastLogLine); 98 }, 99 finish: function(status, summary) { 100 if (!this.log) return; 101 this.lastLogLine.className = status; 102 this.statusCell.innerHTML = status; 103 this.messageCell.innerHTML = this._toHTML(summary); 104 this.addLinksToResults(); 105 }, 106 message: function(message) { 107 if (!this.log) return; 108 this.messageCell.innerHTML = this._toHTML(message); 109 }, 110 summary: function(summary) { 111 if (!this.log) return; 112 this.logsummary.innerHTML = this._toHTML(summary); 113 }, 114 _createLogTable: function() { 115 this.log.innerHTML = 116 '<div id="logsummary"></div>' + 117 '<table id="logtable">' + 118 '<thead><tr><th>
118Status</th><th>Test</th><th>Message</th></tr></thead>' + 119 '<tbody id="loglines"></tbody>' + 120 '</table>'; 121 this.logsummary = $('logsummary') 122 this.loglines = $('loglines'); 123 }, 124 _toHTML: function(txt) { 125 return txt.escapeHTML().replace(/\n/g,"<br/>"); 126 }, 127 addLinksToResults: function(){ 128 $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log 129 td.title = "Run only this test" 130 Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;}); 131 }); 132 $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log 133 td.title = "Run all tests" 134 Event.observe(td, 'click', function(){ window.location.search = "";}); 135 }); 136 } 137} 138 139Test.Unit.Runner = Class.create(); 140Test.Unit.Runner.prototype = { 141 initialize: function(testcases) { 142 this.options = Object.extend({ 143 testLog: 'testlog' 144 }, arguments[1] || {}); 145 this.options.resultsURL = this.parseResultsURLQueryParameter(); 146 this.options.tests = this.parseTestsQueryParameter(); 147 if (this.options.testLog) { 148 this.options.testLog = $(this.options.testLog) || null; 149 } 150 if(this.options.tests) { 151 this.tests = []; 152 for(var i = 0; i < this.options.tests.length; i++) { 153 if(/^test/.test(this.options.tests[i])) { 154 this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"])); 155 } 156 } 157 } else { 158 if (this.options.test) { 159 this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])]; 160 } else { 161 this.tests = []; 162 for(var testcase in testcases) { 163 if(/^test/.test(testcase)) { 164 this.tests.push( 165 new Test.Unit.Testcase( 166 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 167 testcases[testcase], testcases["setup"], testcases["teardown"] 168 )); 169 } 170 } 171 } 172 } 173 this.currentTest = 0; 174 this.logger = new Test.Unit.Logger(this.options.testLog); 175 setTimeout(this.runTests.bind(this), 1000); 176 }, 177 parseResultsURLQueryParameter: function() { 178 return window.location.search.parseQuery()["resultsURL"]; 179 }, 180 parseTestsQueryParameter: function(){ 181 if (window.location.search.parseQuery()["tests"]){ 182 return window.location.search.parseQuery()["tests"].split(','); 183 }; 184 }, 185 // Returns: 186 // "ERROR" if there was an error, 187 // "FAILURE" if there was a failure, or 188 // "SUCCESS" if there was neither 189 getResult: function() { 190 var hasFailure = false; 191 for(var i=0;i<this.tests.length;i++) { 192 if (this.tests[i].errors > 0) { 193 return "ERROR"; 194 } 195 if (this.tests[i].failures > 0) { 196 hasFailure = true; 197 } 198 } 199 if (hasFailure) { 200 return "FAILURE"; 201 } else { 202 return "SUCCESS"; 203 } 204 }, 205 postResults: function() { 206 if (this.options.resultsURL) { 207 new Ajax.Request(this.options.resultsURL, 208 { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false }); 209 } 210 }, 211 runTests: function() { 212 var test = this.tests[this.currentTest]; 213 if (!test) { 214 // finished! 215 this.postResults(); 216 this.logger.summary(this.summary()); 217 return; 218 } 219 if(!test.isWaiting) { 220 this.logger.start(test.name); 221 } 222 test.run(); 223 if(test.isWaiting) { 224 this.logger.message("Waiting for " + test.timeToWait + "ms"); 225 setTimeout(this.runTests.bind(this), test.timeToWait || 1000); 226 } else { 227 this.logger.finish(test.status(), test.summary()); 228 this.currentTest++; 229 // tail recursive, hopefully the browser will skip the stackframe 230 this.runTests(); 231 } 232 }, 233 summary: function() { 234 var assertions = 0; 235 var failures = 0; 236 var errors = 0; 237 var messages = []; 238 for(var i=0;i<this.tests.length;i++) { 239 assertions += this.tests[i].assertions; 240 failures += this.tests[i].failures; 241 errors += this.tests[i].errors; 242 } 243 return ( 244 (this.options.context ? this.options.context + ': ': '') + 245 this.tests.length + " tests, " + 246 assertions + " assertions, " + 247 failures + " failures, " + 248 errors + " errors"); 249 } 250} 251 252Test.Unit.Assertions = Class.create(); 253Test.Unit.Assertions.prototype = { 254 initialize: function() { 255 this.assertions = 0; 256 this.failures = 0; 257 this.errors = 0; 258 this.messages = []; 259 }, 260 summary: function() { 261 return ( 262 this.assertions + " assertions, " + 263 this.failures + " failures, " + 264 this.errors + " errors" + "\n" + 265 this.messages.join("\n")); 266 }, 267 pass: function() { 268 this.assertions++; 269 }, 270 fail: function(message) { 271 this.failures++; 272 this.messages.push("Failure: " + message); 273 }, 274 info: function(message) { 275 this.messages.push("Info: " + message); 276 }, 277 error: function(error) { 278 this.errors++; 279 this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")"); 280 }, 281 status: function() { 282 if (this.failures > 0) return 'failed'; 283 if (this.errors > 0) return 'error'; 284 return 'passed'; 285 }, 286 assert: function(expression) { 287 var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; 288 try { expression ? this.pass() : 289 this.fail(message); } 290 catch(e) { this.error(e); } 291 }, 292 assertEqual: function(expected, actual) { 293 var message = arguments[2] || "assertEqual"; 294 try { (expected == actual) ? this.pass() : 295 this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
296 '", actual "' + Test.Unit.inspect(actual) + '"'); } 297 catch(e) { this.error(e); } 298 }, 299 assertInspect: function(expected, actual) { 300 var message = arguments[2] || "assertInspect"; 301 try { (expected == actual.inspect()) ? this.pass() : 302 this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 303 '", actual "' + Test.Unit.inspect(actual) + '"'); } 304 catch(e) { this.error(e); } 305 }, 306 assertEnumEqual: function(expected, actual) { 307 var message = arguments[2] || "assertEnumEqual"; 308 try { $A(expected).length == $A(actual).length && 309 expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? 310 this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 311 ', actual ' + Test.Unit.inspect(actual)); } 312 catch(e) { this.error(e); } 313 }, 314 assertNotEqual: function(expected, actual) { 315 var message = arguments[2] || "assertNotEqual"; 316 try { (expected != actual) ? this.pass() : 317 this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); } 318 catch(e) { this.error(e); } 319 }, 320 assertIdentical: function(expected, actual) { 321 var message = arguments[2] || "assertIdentical"; 322 try { (expected === actual) ? this.pass() : 323 this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 324 '", actual "' + Test.Unit.inspect(actual) + '"'); } 325 catch(e) { this.error(e); } 326 }, 327 assertNotIdentical: function(expected, actual) { 328 var message = arguments[2] || "assertNotIdentical"; 329 try { !(expected === actual) ? this.pass() : 330 this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 331 '", actual "' + Test.Unit.inspect(actual) + '"'); } 332 catch(e) { this.error(e); } 333 }, 334 assertNull: function(obj) { 335 var message = arguments[1] || 'assertNull' 336 try { (obj==null) ? this.pass() : 337 this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); } 338 catch(e) { this.error(e); } 339 }, 340 assertMatch: function(expected, actual) { 341 var message = arguments[2] || 'assertMatch'; 342 var regex = new RegExp(expected); 343 try { (regex.exec(actual)) ? this.pass() : 344 this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); } 345 catch(e) { this.error(e); } 346 }, 347 assertHidden: function(element) { 348 var message = arguments[1] || 'assertHidden'; 349 this.assertEqual("none", element.style.display, message); 350 }, 351 assertNotNull: function(object) { 352 var message = arguments[1] || 'assertNotNull'; 353 this.assert(object != null, message); 354 }, 355 assertType: function(expected, actual) { 356 var message = arguments[2] || 'assertType'; 357 try { 358 (actual.constructor == expected) ? this.pass() : 359 this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 360 '", actual "' + (actual.constructor) + '"'); } 361 catch(e) { this.error(e); } 362 }, 363 assertNotOfType: function(expected, actual) { 364 var message = arguments[2] || 'assertNotOfType'; 365 try { 366 (actual.constructor != expected) ? this.pass() : 367 this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 368 '", actual "' + (actual.constructor) + '"'); } 369 catch(e) { this.error(e); } 370 }, 371 assertInstanceOf: function(expected, actual) { 372 var message = arguments[2] || 'assertInstanceOf'; 373 try { 374 (actual instanceof expected) ? this.pass() : 375 this.fail(message + ": object was not an instance of the expected type"); } 376 catch(e) { this.error(e); } 377 }, 378 assertNotInstanceOf: function(expected, actual) { 379 var message = arguments[2] || 'assertNotInstanceOf'; 380 try { 381 !(actual instanceof expected) ? this.pass() : 382 this.fail(message + ": object was an instance of the not expected type"); } 383 catch(e) { this.error(e); } 384 }, 385 assertRespondsTo: function(method, obj) { 386 var message = arguments[2] || 'assertRespondsTo'; 387 try { 388 (obj[method] && typeof obj[method] == 'function') ? this.pass() : 389 this.fail(message + ": object doesn't respond to [" + method + "]"); } 390 catch(e) { this.error(e); } 391 }, 392 assertReturnsTrue: function(method, obj) { 393 var message = arguments[2] || 'assertReturnsTrue'; 394 try { 395 var m = obj[method]; 396 if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; 397 m() ? this.pass() : 398 this.fail(message + ": method returned false"); } 399 catch(e) { this.error(e); } 400 }, 401 assertReturnsFalse: function(method, obj) { 402 var message = arguments[2] || 'assertReturnsFalse'; 403 try { 404 var m = obj[method]; 405 if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; 406 !m() ? this.pass() : 407 this.fail(message + ": method returned true"); } 408 catch(e) { this.error(e); } 409 }, 410 assertRaise: function(exceptionName, method) { 411 var message = arguments[2] || 'assertRaise'; 412 try { 413 method(); 414 this.fail(message + ": exception expected but none was raised"); } 415 catch(e) { 416 ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 417 } 418 }, 419 assertElementsMatch: function() { 420 var expressions = $A(arguments), elements = $A(expressions.shift()); 421 if (elements.length != expressions.length) { 422 this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions'); 423 return false; 424 } 425 elements.zip(expressions).all(function(pair, index) { 426 var element = $(pair.first()), expression = pair.last(); 427 if (element.match(expression)) return true; 428 this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect()); 429 }.bind(this)) && this.pass(); 430 }, 431 assertElementMatches: function(element, expression) { 432 this.assertElementsMatch([element], expression); 433 }, 434 benchmark: function(operation, iterations) { 435 var startAt = new Date(); 436 (iterations || 1).times(operation); 437 var timeTaken = ((new Date())-startAt); 438 this.info((arguments[2] || 'Operation') + ' finished ' + 439 iterations + ' iterations in ' + (timeTaken/1000)+'s' ); 440 return timeTaken; 441 }, 442 _isVisible: function(element) { 443 element = $(element); 444 if(!element.parentNode) return true; 445 this.assertNotNull(element); 446 if(element.style && Element.getStyle(element, 'display') == 'none') 447 return false; 448 449 return this._isVisible(element.parentNode); 450 }, 451 assertNotVisible: function(element) { 452 this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1])); 453 }, 454 assertVisible: function(element) { 455 this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1])); 456 }, 457 benchmark: function(operation, iterations) { 458 var startAt = new Date(); 459 (iterations || 1).times(operation); 460 var timeTaken = ((new Date())-startAt); 461 this.info((arguments[2] || 'Operation') + ' finished ' + 462 iterations + ' iterations in ' + (timeTaken/1000)+'s' ); 463 return timeTaken; 464 } 465} 466 467Test.Unit.Testcase = Class.create(); 468Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), { 469 initialize: function(name, test, setup, teardown) { 470 Test.Unit.Assertions.prototype.initialize.bind(this)();
471 this.name = name; 472 473 if(typeof test == 'string') { 474 test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,'); 475 test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)'); 476 this.test = function() { 477 eval('with(this){'+test+'}'); 478 } 479 } else { 480 this.test = test || function() {}; 481 } 482 483 this.setup = setup || function() {}; 484 this.teardown = teardown || function() {}; 485 this.isWaiting = false; 486 this.timeToWait = 1000; 487 }, 488 wait: function(time, nextPart) { 489 this.isWaiting = true; 490 this.test = nextPart; 491 this.timeToWait = time; 492 }, 493 run: function() { 494 try { 495 try { 496 if (!this.isWaiting) this.setup.bind(this)(); 497 this.isWaiting = false; 498 this.test.bind(this)(); 499 } finally { 500 if(!this.isWaiting) { 501 this.teardown.bind(this)(); 502 } 503 } 504 } 505 catch(e) { this.error(e); } 506 } 507}); 508 509// *EXPERIMENTAL* BDD-style testing to please non-technical folk 510// This draws many ideas from RSpec http://rspec.rubyforge.org/ 511 512Test.setupBDDExtensionMethods = function(){ 513 var METHODMAP = { 514 shouldEqual: 'assertEqual', 515 shouldNotEqual: 'assertNotEqual', 516 shouldEqualEnum: 'assertEnumEqual', 517 shouldBeA: 'assertType', 518 shouldNotBeA: 'assertNotOfType', 519 shouldBeAn: 'assertType', 520 shouldNotBeAn: 'assertNotOfType', 521 shouldBeNull: 'assertNull', 522 shouldNotBeNull: 'assertNotNull', 523 524 shouldBe: 'assertReturnsTrue', 525 shouldNotBe: 'assertReturnsFalse', 526 shouldRespondTo: 'assertRespondsTo' 527 }; 528 Test.BDDMethods = {}; 529 for(m in METHODMAP) { 530 Test.BDDMethods[m] = eval( 531 'function(){'+ 532 'var args = $A(arguments);'+ 533 'var scope = args.shift();'+ 534 'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }'); 535 } 536 [Array.prototype, String.prototype, Number.prototype].each( 537 function(p){ Object.extend(p, Test.BDDMethods) } 538 ); 539} 540 541Test.context = function(name, spec, log){ 542 Test.setupBDDExtensionMethods(); 543 544 var compiledSpec = {}; 545 var titles = {}; 546 for(specName in spec) { 547 switch(specName){ 548 case "setup": 549 case "teardown": 550 compiledSpec[specName] = spec[specName]; 551 break; 552 default: 553 var testName = 'test'+specName.gsub(/\s+/,'-').camelize(); 554 var body = spec[specName].toString().split('\n').slice(1); 555 if(/^\{/.test(body[0])) body = body.slice(1); 556 body.pop(); 557 body = body.map(function(statement){ 558 return statement.strip() 559 }); 560 compiledSpec[testName] = body.join('\n'); 561 titles[testName] = specName; 562 } 563 } 564 new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name }); 565};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.