michael@0: // script.aculo.us unittest.js v1.7.1_beta2, Tue May 15 15:15:45 EDT 2007 michael@0: michael@0: // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) michael@0: // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) michael@0: // (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/) michael@0: // michael@0: // script.aculo.us is freely distributable under the terms of an MIT-style license. michael@0: // For details, see the script.aculo.us web site: http://script.aculo.us/ michael@0: michael@0: // experimental, Firefox-only michael@0: Event.simulateMouse = function(element, eventName) { michael@0: var options = Object.extend({ michael@0: pointerX: 0, michael@0: pointerY: 0, michael@0: buttons: 0, michael@0: ctrlKey: false, michael@0: altKey: false, michael@0: shiftKey: false, michael@0: metaKey: false michael@0: }, arguments[2] || {}); michael@0: var oEvent = document.createEvent("MouseEvents"); michael@0: oEvent.initMouseEvent(eventName, true, true, document.defaultView, michael@0: options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, michael@0: options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element)); michael@0: michael@0: if(this.mark) Element.remove(this.mark); michael@0: this.mark = document.createElement('div'); michael@0: this.mark.appendChild(document.createTextNode(" ")); michael@0: document.body.appendChild(this.mark); michael@0: this.mark.style.position = 'absolute'; michael@0: this.mark.style.top = options.pointerY + "px"; michael@0: this.mark.style.left = options.pointerX + "px"; michael@0: this.mark.style.width = "5px"; michael@0: this.mark.style.height = "5px;"; michael@0: this.mark.style.borderTop = "1px solid red;" michael@0: this.mark.style.borderLeft = "1px solid red;" michael@0: michael@0: if(this.step) michael@0: alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options)); michael@0: michael@0: $(element).dispatchEvent(oEvent); michael@0: }; michael@0: michael@0: // 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. michael@0: // You need to downgrade to 1.0.4 for now to get this working michael@0: // See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much michael@0: Event.simulateKey = function(element, eventName) { michael@0: var options = Object.extend({ michael@0: ctrlKey: false, michael@0: altKey: false, michael@0: shiftKey: false, michael@0: metaKey: false, michael@0: keyCode: 0, michael@0: charCode: 0 michael@0: }, arguments[2] || {}); michael@0: michael@0: var oEvent = document.createEvent("KeyEvents"); michael@0: oEvent.initKeyEvent(eventName, true, true, window, michael@0: options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, michael@0: options.keyCode, options.charCode ); michael@0: $(element).dispatchEvent(oEvent); michael@0: }; michael@0: michael@0: Event.simulateKeys = function(element, command) { michael@0: for(var i=0; i' + michael@0: '' + michael@0: '' + michael@0: '' + michael@0: '
StatusTestMessage
'; michael@0: this.logsummary = $('logsummary') michael@0: this.loglines = $('loglines'); michael@0: }, michael@0: _toHTML: function(txt) { michael@0: return txt.escapeHTML().replace(/\n/g,"
"); michael@0: }, michael@0: addLinksToResults: function(){ michael@0: $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log michael@0: td.title = "Run only this test" michael@0: Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;}); michael@0: }); michael@0: $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log michael@0: td.title = "Run all tests" michael@0: Event.observe(td, 'click', function(){ window.location.search = "";}); michael@0: }); michael@0: } michael@0: } michael@0: michael@0: Test.Unit.Runner = Class.create(); michael@0: Test.Unit.Runner.prototype = { michael@0: initialize: function(testcases) { michael@0: this.options = Object.extend({ michael@0: testLog: 'testlog' michael@0: }, arguments[1] || {}); michael@0: this.options.resultsURL = this.parseResultsURLQueryParameter(); michael@0: this.options.tests = this.parseTestsQueryParameter(); michael@0: if (this.options.testLog) { michael@0: this.options.testLog = $(this.options.testLog) || null; michael@0: } michael@0: if(this.options.tests) { michael@0: this.tests = []; michael@0: for(var i = 0; i < this.options.tests.length; i++) { michael@0: if(/^test/.test(this.options.tests[i])) { michael@0: this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"])); michael@0: } michael@0: } michael@0: } else { michael@0: if (this.options.test) { michael@0: this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])]; michael@0: } else { michael@0: this.tests = []; michael@0: for(var testcase in testcases) { michael@0: if(/^test/.test(testcase)) { michael@0: this.tests.push( michael@0: new Test.Unit.Testcase( michael@0: this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, michael@0: testcases[testcase], testcases["setup"], testcases["teardown"] michael@0: )); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: this.currentTest = 0; michael@0: this.logger = new Test.Unit.Logger(this.options.testLog); michael@0: setTimeout(this.runTests.bind(this), 1000); michael@0: }, michael@0: parseResultsURLQueryParameter: function() { michael@0: return window.location.search.parseQuery()["resultsURL"]; michael@0: }, michael@0: parseTestsQueryParameter: function(){ michael@0: if (window.location.search.parseQuery()["tests"]){ michael@0: return window.location.search.parseQuery()["tests"].split(','); michael@0: }; michael@0: }, michael@0: // Returns: michael@0: // "ERROR" if there was an error, michael@0: // "FAILURE" if there was a failure, or michael@0: // "SUCCESS" if there was neither michael@0: getResult: function() { michael@0: var hasFailure = false; michael@0: for(var i=0;i 0) { michael@0: return "ERROR"; michael@0: } michael@0: if (this.tests[i].failures > 0) { michael@0: hasFailure = true; michael@0: } michael@0: } michael@0: if (hasFailure) { michael@0: return "FAILURE"; michael@0: } else { michael@0: return "SUCCESS"; michael@0: } michael@0: }, michael@0: postResults: function() { michael@0: if (this.options.resultsURL) { michael@0: new Ajax.Request(this.options.resultsURL, michael@0: { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false }); michael@0: } michael@0: }, michael@0: runTests: function() { michael@0: var test = this.tests[this.currentTest]; michael@0: if (!test) { michael@0: // finished! michael@0: this.postResults(); michael@0: this.logger.summary(this.summary()); michael@0: return; michael@0: } michael@0: if(!test.isWaiting) { michael@0: this.logger.start(test.name); michael@0: } michael@0: test.run(); michael@0: if(test.isWaiting) { michael@0: this.logger.message("Waiting for " + test.timeToWait + "ms"); michael@0: setTimeout(this.runTests.bind(this), test.timeToWait || 1000); michael@0: } else { michael@0: this.logger.finish(test.status(), test.summary()); michael@0: this.currentTest++; michael@0: // tail recursive, hopefully the browser will skip the stackframe michael@0: this.runTests(); michael@0: } michael@0: }, michael@0: summary: function() { michael@0: var assertions = 0; michael@0: var failures = 0; michael@0: var errors = 0; michael@0: var messages = []; michael@0: for(var i=0;i 0) return 'failed'; michael@0: if (this.errors > 0) return 'error'; michael@0: return 'passed'; michael@0: }, michael@0: assert: function(expression) { michael@0: var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; michael@0: try { expression ? this.pass() : michael@0: this.fail(message); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertEqual: function(expected, actual) { michael@0: var message = arguments[2] || "assertEqual"; michael@0: try { (expected == actual) ? this.pass() : michael@0: this.fail(message + ': expected "' + Test.Unit.inspect(expected) + michael@0: '", actual "' + Test.Unit.inspect(actual) + '"'); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertInspect: function(expected, actual) { michael@0: var message = arguments[2] || "assertInspect"; michael@0: try { (expected == actual.inspect()) ? this.pass() : michael@0: this.fail(message + ': expected "' + Test.Unit.inspect(expected) + michael@0: '", actual "' + Test.Unit.inspect(actual) + '"'); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertEnumEqual: function(expected, actual) { michael@0: var message = arguments[2] || "assertEnumEqual"; michael@0: try { $A(expected).length == $A(actual).length && michael@0: expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? michael@0: this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + michael@0: ', actual ' + Test.Unit.inspect(actual)); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertNotEqual: function(expected, actual) { michael@0: var message = arguments[2] || "assertNotEqual"; michael@0: try { (expected != actual) ? this.pass() : michael@0: this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertIdentical: function(expected, actual) { michael@0: var message = arguments[2] || "assertIdentical"; michael@0: try { (expected === actual) ? this.pass() : michael@0: this.fail(message + ': expected "' + Test.Unit.inspect(expected) + michael@0: '", actual "' + Test.Unit.inspect(actual) + '"'); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertNotIdentical: function(expected, actual) { michael@0: var message = arguments[2] || "assertNotIdentical"; michael@0: try { !(expected === actual) ? this.pass() : michael@0: this.fail(message + ': expected "' + Test.Unit.inspect(expected) + michael@0: '", actual "' + Test.Unit.inspect(actual) + '"'); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertNull: function(obj) { michael@0: var message = arguments[1] || 'assertNull' michael@0: try { (obj==null) ? this.pass() : michael@0: this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertMatch: function(expected, actual) { michael@0: var message = arguments[2] || 'assertMatch'; michael@0: var regex = new RegExp(expected); michael@0: try { (regex.exec(actual)) ? this.pass() : michael@0: this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertHidden: function(element) { michael@0: var message = arguments[1] || 'assertHidden'; michael@0: this.assertEqual("none", element.style.display, message); michael@0: }, michael@0: assertNotNull: function(object) { michael@0: var message = arguments[1] || 'assertNotNull'; michael@0: this.assert(object != null, message); michael@0: }, michael@0: assertType: function(expected, actual) { michael@0: var message = arguments[2] || 'assertType'; michael@0: try { michael@0: (actual.constructor == expected) ? this.pass() : michael@0: this.fail(message + ': expected "' + Test.Unit.inspect(expected) + michael@0: '", actual "' + (actual.constructor) + '"'); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertNotOfType: function(expected, actual) { michael@0: var message = arguments[2] || 'assertNotOfType'; michael@0: try { michael@0: (actual.constructor != expected) ? this.pass() : michael@0: this.fail(message + ': expected "' + Test.Unit.inspect(expected) + michael@0: '", actual "' + (actual.constructor) + '"'); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertInstanceOf: function(expected, actual) { michael@0: var message = arguments[2] || 'assertInstanceOf'; michael@0: try { michael@0: (actual instanceof expected) ? this.pass() : michael@0: this.fail(message + ": object was not an instance of the expected type"); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertNotInstanceOf: function(expected, actual) { michael@0: var message = arguments[2] || 'assertNotInstanceOf'; michael@0: try { michael@0: !(actual instanceof expected) ? this.pass() : michael@0: this.fail(message + ": object was an instance of the not expected type"); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertRespondsTo: function(method, obj) { michael@0: var message = arguments[2] || 'assertRespondsTo'; michael@0: try { michael@0: (obj[method] && typeof obj[method] == 'function') ? this.pass() : michael@0: this.fail(message + ": object doesn't respond to [" + method + "]"); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertReturnsTrue: function(method, obj) { michael@0: var message = arguments[2] || 'assertReturnsTrue'; michael@0: try { michael@0: var m = obj[method]; michael@0: if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; michael@0: m() ? this.pass() : michael@0: this.fail(message + ": method returned false"); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertReturnsFalse: function(method, obj) { michael@0: var message = arguments[2] || 'assertReturnsFalse'; michael@0: try { michael@0: var m = obj[method]; michael@0: if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; michael@0: !m() ? this.pass() : michael@0: this.fail(message + ": method returned true"); } michael@0: catch(e) { this.error(e); } michael@0: }, michael@0: assertRaise: function(exceptionName, method) { michael@0: var message = arguments[2] || 'assertRaise'; michael@0: try { michael@0: method(); michael@0: this.fail(message + ": exception expected but none was raised"); } michael@0: catch(e) { michael@0: ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); michael@0: } michael@0: }, michael@0: assertElementsMatch: function() { michael@0: var expressions = $A(arguments), elements = $A(expressions.shift()); michael@0: if (elements.length != expressions.length) { michael@0: this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions'); michael@0: return false; michael@0: } michael@0: elements.zip(expressions).all(function(pair, index) { michael@0: var element = $(pair.first()), expression = pair.last(); michael@0: if (element.match(expression)) return true; michael@0: this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect()); michael@0: }.bind(this)) && this.pass(); michael@0: }, michael@0: assertElementMatches: function(element, expression) { michael@0: this.assertElementsMatch([element], expression); michael@0: }, michael@0: benchmark: function(operation, iterations) { michael@0: var startAt = new Date(); michael@0: (iterations || 1).times(operation); michael@0: var timeTaken = ((new Date())-startAt); michael@0: this.info((arguments[2] || 'Operation') + ' finished ' + michael@0: iterations + ' iterations in ' + (timeTaken/1000)+'s' ); michael@0: return timeTaken; michael@0: }, michael@0: _isVisible: function(element) { michael@0: element = $(element); michael@0: if(!element.parentNode) return true; michael@0: this.assertNotNull(element); michael@0: if(element.style && Element.getStyle(element, 'display') == 'none') michael@0: return false; michael@0: michael@0: return this._isVisible(element.parentNode); michael@0: }, michael@0: assertNotVisible: function(element) { michael@0: this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1])); michael@0: }, michael@0: assertVisible: function(element) { michael@0: this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1])); michael@0: }, michael@0: benchmark: function(operation, iterations) { michael@0: var startAt = new Date(); michael@0: (iterations || 1).times(operation); michael@0: var timeTaken = ((new Date())-startAt); michael@0: this.info((arguments[2] || 'Operation') + ' finished ' + michael@0: iterations + ' iterations in ' + (timeTaken/1000)+'s' ); michael@0: return timeTaken; michael@0: } michael@0: } michael@0: michael@0: Test.Unit.Testcase = Class.create(); michael@0: Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), { michael@0: initialize: function(name, test, setup, teardown) { michael@0: Test.Unit.Assertions.prototype.initialize.bind(this)(); michael@0: this.name = name; michael@0: michael@0: if(typeof test == 'string') { michael@0: test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,'); michael@0: test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)'); michael@0: this.test = function() { michael@0: eval('with(this){'+test+'}'); michael@0: } michael@0: } else { michael@0: this.test = test || function() {}; michael@0: } michael@0: michael@0: this.setup = setup || function() {}; michael@0: this.teardown = teardown || function() {}; michael@0: this.isWaiting = false; michael@0: this.timeToWait = 1000; michael@0: }, michael@0: wait: function(time, nextPart) { michael@0: this.isWaiting = true; michael@0: this.test = nextPart; michael@0: this.timeToWait = time; michael@0: }, michael@0: run: function() { michael@0: try { michael@0: try { michael@0: if (!this.isWaiting) this.setup.bind(this)(); michael@0: this.isWaiting = false; michael@0: this.test.bind(this)(); michael@0: } finally { michael@0: if(!this.isWaiting) { michael@0: this.teardown.bind(this)(); michael@0: } michael@0: } michael@0: } michael@0: catch(e) { this.error(e); } michael@0: } michael@0: }); michael@0: michael@0: // *EXPERIMENTAL* BDD-style testing to please non-technical folk michael@0: // This draws many ideas from RSpec http://rspec.rubyforge.org/ michael@0: michael@0: Test.setupBDDExtensionMethods = function(){ michael@0: var METHODMAP = { michael@0: shouldEqual: 'assertEqual', michael@0: shouldNotEqual: 'assertNotEqual', michael@0: shouldEqualEnum: 'assertEnumEqual', michael@0: shouldBeA: 'assertType', michael@0: shouldNotBeA: 'assertNotOfType', michael@0: shouldBeAn: 'assertType', michael@0: shouldNotBeAn: 'assertNotOfType', michael@0: shouldBeNull: 'assertNull', michael@0: shouldNotBeNull: 'assertNotNull', michael@0: michael@0: shouldBe: 'assertReturnsTrue', michael@0: shouldNotBe: 'assertReturnsFalse', michael@0: shouldRespondTo: 'assertRespondsTo' michael@0: }; michael@0: Test.BDDMethods = {}; michael@0: for(m in METHODMAP) { michael@0: Test.BDDMethods[m] = eval( michael@0: 'function(){'+ michael@0: 'var args = $A(arguments);'+ michael@0: 'var scope = args.shift();'+ michael@0: 'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }'); michael@0: } michael@0: [Array.prototype, String.prototype, Number.prototype].each( michael@0: function(p){ Object.extend(p, Test.BDDMethods) } michael@0: ); michael@0: } michael@0: michael@0: Test.context = function(name, spec, log){ michael@0: Test.setupBDDExtensionMethods(); michael@0: michael@0: var compiledSpec = {}; michael@0: var titles = {}; michael@0: for(specName in spec) { michael@0: switch(specName){ michael@0: case "setup": michael@0: case "teardown": michael@0: compiledSpec[specName] = spec[specName]; michael@0: break; michael@0: default: michael@0: var testName = 'test'+specName.gsub(/\s+/,'-').camelize(); michael@0: var body = spec[specName].toString().split('\n').slice(1); michael@0: if(/^\{/.test(body[0])) body = body.slice(1); michael@0: body.pop(); michael@0: body = body.map(function(statement){ michael@0: return statement.strip() michael@0: }); michael@0: compiledSpec[testName] = body.join('\n'); michael@0: titles[testName] = specName; michael@0: } michael@0: } michael@0: new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name }); michael@0: }; michael@0: michael@0: if ( parent.SimpleTest && parent.runAJAXTest ) (function(){ michael@0: var finish = Test.Unit.Logger.prototype.finish; michael@0: Test.Unit.Logger.prototype.finish = function(status,summary){ michael@0: var match = summary.match(/(\d+).*(\d+).*(\d+)/) michael@0: var fail = parseInt(match[2]) + parseInt(match[3]); michael@0: var pass = match[1] - fail; michael@0: michael@0: for ( var i = 0; i < pass; i++ ) michael@0: parent.SimpleTest.ok( true, this.testName, summary ); michael@0: michael@0: for ( var i = 0; i < fail; i++ ) michael@0: parent.SimpleTest.ok( false, this.testName, summary ); michael@0: michael@0: return finish.apply( this, arguments ); michael@0: }; michael@0: michael@0: // Intentionally overwrite (to stop the Ajax request) michael@0: Test.Unit.Runner.prototype.postResults = parent.runAJAXTest; michael@0: michael@0: Ajax.InPlaceEditor.prototype.onFailure = function(){}; michael@0: })();