UNPKG

log4javascript

Version:
739 lines (624 loc) 20.3 kB
// Next three methods are primarily for IE5, which is missing them if (!Array.prototype.push) { Array.prototype.push = function() { for (var i = 0; i < arguments.length; i++){ this[this.length] = arguments[i]; } return this.length; }; } if (!Array.prototype.shift) { Array.prototype.shift = function() { if (this.length > 0) { var firstItem = this[0]; for (var i = 0; i < this.length - 1; i++) { this[i] = this[i + 1]; } this.length = this.length - 1; return firstItem; } }; } if (!Function.prototype.apply) { Function.prototype.apply = function(obj, args) { var methodName = "__apply__"; if (typeof obj[methodName] != "undefined") { methodName += (String(Math.random())).substr(2); } obj[methodName] = this; var argsStrings = new Array(args.length); for (var i = 0; i < args.length; i++) { argsStrings[i] = "args[" + i + "]"; } var script = "obj." + methodName + "(" + argsStrings.join(",") + ")"; var returnValue = eval(script); delete obj[methodName]; return returnValue; }; } /* -------------------------------------------------------------------------- */ var xn = new Object(); (function() { // Utility functions // Event listeners var getListenersPropertyName = function(eventName) { return "__listeners__" + eventName; }; var addEventListener = function(node, eventName, listener, useCapture) { useCapture = Boolean(useCapture); if (node.addEventListener) { node.addEventListener(eventName, listener, useCapture); } else if (node.attachEvent) { node.attachEvent("on" + eventName, listener); } else { var propertyName = getListenersPropertyName(eventName); if (!node[propertyName]) { node[propertyName] = new Array(); // Set event handler node["on" + eventName] = function(evt) { evt = module.getEvent(evt); var listenersPropertyName = getListenersPropertyName(eventName); // Clone the array of listeners to leave the original untouched var listeners = cloneArray(this[listenersPropertyName]); var currentListener; // Call each listener in turn while (currentListener = listeners.shift()) { currentListener.call(this, evt); } }; } node[propertyName].push(listener); } }; // Clones an array var cloneArray = function(arr) { var clonedArray = []; for (var i = 0; i < arr.length; i++) { clonedArray[i] = arr[i]; } return clonedArray; } var isFunction = function(f) { if (!f){ return false; } return (f instanceof Function || typeof f == "function"); }; // CSS Utilities function array_contains(arr, val) { for (var i = 0, len = arr.length; i < len; i++) { if (arr[i] === val) { return true; } } return false; } function addClass(el, cssClass) { if (!hasClass(el, cssClass)) { if (el.className) { el.className += " " + cssClass; } else { el.className = cssClass; } } } function hasClass(el, cssClass) { if (el.className) { var classNames = el.className.split(" "); return array_contains(classNames, cssClass); } return false; } function removeClass(el, cssClass) { if (hasClass(el, cssClass)) { // Rebuild the className property var existingClasses = el.className.split(" "); var newClasses = []; for (var i = 0; i < existingClasses.length; i++) { if (existingClasses[i] != cssClass) { newClasses[newClasses.length] = existingClasses[i]; } } el.className = newClasses.join(" "); } } function replaceClass(el, newCssClass, oldCssClass) { removeClass(el, oldCssClass); addClass(el, newCssClass); } function getExceptionStringRep(ex) { if (ex) { var exStr = "Exception: "; if (ex.message) { exStr += ex.message; } else if (ex.description) { exStr += ex.description; } if (ex.lineNumber) { exStr += " on line number " + ex.lineNumber; } if (ex.fileName) { exStr += " in file " + ex.fileName; } return exStr; } return null; } /* ---------------------------------------------------------------------- */ /* Configure the test logger try to use FireBug */ var log, error; if (window["console"] && typeof console.log == "function") { log = function() { if (xn.test.enableTestDebug) { console.log.apply(console, arguments); } }; error = function() { if (xn.test.enableTestDebug) { console.error.apply(console, arguments); } }; } else { log = function() {}; } /* Set up something to report to */ var initialized = false; var container; var progressBarContainer, progressBar, overallSummaryText; var currentTest = null; var suites = []; var totalTestCount = 0; var currentTestIndex = 0; var testFailed = false; var testsPassedCount = 0; var startTime; var log4javascriptEnabled = false; var nextSuiteIndex = 0; function runNextSuite() { if (nextSuiteIndex < suites.length) { suites[nextSuiteIndex++].run(); } } var init = function() { if (initialized) { return true; } container = document.createElement("div"); // Create the overall progress bar progressBarContainer = container.appendChild(document.createElement("div")); progressBarContainer.className = "xn_test_progressbar_container xn_test_overallprogressbar_container"; progressBar = progressBarContainer.appendChild(document.createElement("div")); progressBar.className = "success"; document.body.appendChild(container); var h1 = progressBar.appendChild(document.createElement("h1")); overallSummaryText = h1.appendChild(document.createTextNode("")); initialized = true; // Set up logging log4javascriptEnabled = !!log4javascript && xn.test.enable_log4javascript; function TestLogAppender() {} if (log4javascriptEnabled) { TestLogAppender.prototype = new log4javascript.Appender(); TestLogAppender.prototype.layout = new log4javascript.PatternLayout("%d{HH:mm:ss,SSS} %-5p %m"); TestLogAppender.prototype.append = function(loggingEvent) { var formattedMessage = this.getLayout().format(loggingEvent); if (this.getLayout().ignoresThrowable()) { formattedMessage += loggingEvent.getThrowableStrRep(); } currentTest.addLogMessage(formattedMessage); }; var appender = new TestLogAppender(); appender.setThreshold(log4javascript.Level.ALL); log4javascript.getRootLogger().addAppender(appender); log4javascript.getRootLogger().setLevel(log4javascript.Level.ALL); } startTime = new Date(); // First, build each suite for (var i = 0; i < suites.length; i++) { suites[i].build(); totalTestCount += suites[i].tests.length; } // Now run each suite runNextSuite(); }; function updateProgressBar() { progressBar.style.width = "" + parseInt(100 * (currentTestIndex) / totalTestCount) + "%"; var s = (totalTestCount === 1) ? "" : "s"; var timeTaken = new Date().getTime() - startTime.getTime(); overallSummaryText.nodeValue = "" + testsPassedCount + " of " + totalTestCount + " test" + s + " passed in " + timeTaken + "ms"; } addEventListener(window, "load", init); /* ---------------------------------------------------------------------- */ /* Test Suite */ var Suite = function(name, callback, hideSuccessful) { this.name = name; this.callback = callback; this.hideSuccessful = hideSuccessful; this.tests = []; this.log = log; this.error = error; this.expanded = true; suites.push(this); } Suite.prototype.test = function(name, callback, setUp, tearDown) { this.log("adding a test named " + name) var t = new Test(name, callback, this, setUp, tearDown); this.tests.push(t); }; Suite.prototype.build = function() { // Build the elements used by the suite var suite = this; this.testFailed = false; this.container = document.createElement("div"); this.container.className = "xn_test_suite_container"; var heading = document.createElement("h2"); this.expander = document.createElement("span"); this.expander.className = "xn_test_expander"; this.expander.onclick = function() { if (suite.expanded) { suite.collapse(); } else { suite.expand(); } }; heading.appendChild(this.expander); this.headingTextNode = document.createTextNode(this.name); heading.appendChild(this.headingTextNode); this.container.appendChild(heading); this.reportContainer = document.createElement("dl"); this.container.appendChild(this.reportContainer); this.progressBarContainer = document.createElement("div"); this.progressBarContainer.className = "xn_test_progressbar_container"; this.progressBar = document.createElement("div"); this.progressBar.className = "success"; this.progressBar.innerHTML = "&nbsp;"; this.progressBarContainer.appendChild(this.progressBar); this.reportContainer.appendChild(this.progressBarContainer); this.expand(); container.appendChild(this.container); // invoke callback to build the tests this.callback.apply(this, [this]); }; Suite.prototype.run = function() { this.log("running suite '%s'", this.name) this.startTime = new Date(); // now run the first test this._currentIndex = 0; this.runNextTest(); }; Suite.prototype.updateProgressBar = function() { // Update progress bar this.progressBar.style.width = "" + parseInt(100 * (this._currentIndex) / this.tests.length) + "%"; //log(this._currentIndex + ", " + this.tests.length + ", " + progressBar.style.width + ", " + progressBar.className); }; Suite.prototype.expand = function() { this.expander.innerHTML = "-"; replaceClass(this.reportContainer, "xn_test_expanded", "xn_test_collapsed"); this.expanded = true; }; Suite.prototype.collapse = function() { this.expander.innerHTML = "+"; replaceClass(this.reportContainer, "xn_test_collapsed", "xn_test_expanded"); this.expanded = false; }; Suite.prototype.finish = function(timeTaken) { var newClass = this.testFailed ? "xn_test_suite_failure" : "xn_test_suite_success"; var oldClass = this.testFailed ? "xn_test_suite_success" : "xn_test_suite_failure"; replaceClass(this.container, newClass, oldClass); this.headingTextNode.nodeValue += " (" + timeTaken + "ms)"; if (this.hideSuccessful && !this.testFailed) { this.collapse(); } runNextSuite(); }; /** * Works recursively with external state (the next index) * so that we can handle async tests differently */ Suite.prototype.runNextTest = function() { if (this._currentIndex == this.tests.length) { // finished! var timeTaken = new Date().getTime() - this.startTime.getTime(); this.finish(timeTaken); return; } var suite = this; var t = this.tests[this._currentIndex++]; currentTestIndex++; if (isFunction(suite.setUp)) { suite.setUp.apply(suite, [t]); } if (isFunction(t.setUp)) { t.setUp.apply(t, [t]); } t._run(); function afterTest() { if (isFunction(suite.tearDown)) { suite.tearDown.apply(suite, [t]); } if (isFunction(t.tearDown)) { t.tearDown.apply(t, [t]); } suite.log("finished test [%s]", t.name); updateProgressBar(); suite.updateProgressBar(); suite.runNextTest(); } if (t.isAsync) { t.whenFinished = afterTest; } else { setTimeout(afterTest, 1); } }; Suite.prototype.reportSuccess = function() { }; /* ---------------------------------------------------------------------- */ /** * Create a new test */ var Test = function(name, callback, suite, setUp, tearDown) { this.name = name; this.callback = callback; this.suite = suite; this.setUp = setUp; this.tearDown = tearDown; this.log = log; this.error = error; this.assertCount = 0; this.logMessages = []; this.logExpanded = false; }; /** * Default success reporter, please override */ Test.prototype.reportSuccess = function(name, timeTaken) { /* default success reporting handler */ this.reportHeading = document.createElement("dt"); var text = this.name + " passed in " + timeTaken + "ms"; this.reportHeading.appendChild(document.createTextNode(text)); this.reportHeading.className = "success"; var dd = document.createElement("dd"); dd.className = "success"; this.suite.reportContainer.appendChild(this.reportHeading); this.suite.reportContainer.appendChild(dd); this.createLogReport(); }; /** * Cause the test to immediately fail */ Test.prototype.reportFailure = function(name, msg, ex) { this.suite.testFailed = true; this.suite.progressBar.className = "failure"; progressBar.className = "failure"; this.reportHeading = document.createElement("dt"); this.reportHeading.className = "failure"; var text = document.createTextNode(this.name); this.reportHeading.appendChild(text); var dd = document.createElement("dd"); dd.appendChild(document.createTextNode(msg)); dd.className = "failure"; this.suite.reportContainer.appendChild(this.reportHeading); this.suite.reportContainer.appendChild(dd); if (ex && ex.stack) { var stackTraceContainer = this.suite.reportContainer.appendChild(document.createElement("code")); stackTraceContainer.className = "xn_test_stacktrace"; stackTraceContainer.innerHTML = ex.stack.replace(/\r/g, "\n").replace(/\n{1,2}/g, "<br />"); } this.createLogReport(); }; Test.prototype.createLogReport = function() { if (this.logMessages.length > 0) { this.reportHeading.appendChild(document.createTextNode(" (")); var logToggler = this.reportHeading.appendChild(document.createElement("a")); logToggler.href = "#"; logToggler.innerHTML = "show log"; var test = this; logToggler.onclick = function() { if (test.logExpanded) { test.hideLogReport(); this.innerHTML = "show log"; test.logExpanded = false; } else { test.showLogReport(); this.innerHTML = "hide log"; test.logExpanded = true; } return false; }; this.reportHeading.appendChild(document.createTextNode(")")); // Create log report this.logReport = this.suite.reportContainer.appendChild(document.createElement("pre")); this.logReport.style.display = "none"; this.logReport.className = "xn_test_log_report"; var logMessageDiv; for (var i = 0, len = this.logMessages.length; i < len; i++) { logMessageDiv = this.logReport.appendChild(document.createElement("div")); logMessageDiv.appendChild(document.createTextNode(this.logMessages[i])); } } }; Test.prototype.showLogReport = function() { this.logReport.style.display = "inline-block"; }; Test.prototype.hideLogReport = function() { this.logReport.style.display = "none"; }; Test.prototype.async = function(timeout, callback) { timeout = timeout || 250; var self = this; var timedOutFunc = function() { if (!self.completed) { var message = (typeof callback === "undefined") ? "Asynchronous test timed out" : callback(self); self.fail(message); } } var timer = setTimeout(function () { timedOutFunc.apply(self, []); }, timeout) this.isAsync = true; }; /** * Run the test */ Test.prototype._run = function() { this.log("starting test [%s]", this.name); this.startTime = new Date(); currentTest = this; try { this.callback(this); if (!this.completed && !this.isAsync) { this.succeed(); } } catch (e) { this.log("test [%s] threw exception [%s]", this.name, e); var s = (this.assertCount === 1) ? "" : "s"; this.fail("Exception thrown after " + this.assertCount + " successful assertion" + s + ": " + getExceptionStringRep(e), e); } }; /** * Cause the test to immediately succeed */ Test.prototype.succeed = function() { if (this.completed) { return false; } // this.log("test [%s] succeeded", this.name); this.completed = true; var timeTaken = new Date().getTime() - this.startTime.getTime(); testsPassedCount++; this.reportSuccess(this.name, timeTaken); if (this.whenFinished) { this.whenFinished(); } }; Test.prototype.fail = function(msg, ex) { if (typeof msg != "string") { msg = getExceptionStringRep(msg); } if (this.completed) { return false; } this.completed = true; // this.log("test [%s] failed", this.name); this.reportFailure(this.name, msg, ex); if (this.whenFinished) { this.whenFinished(); } }; Test.prototype.addLogMessage = function(logMessage) { this.logMessages.push(logMessage); }; /* assertions */ var displayStringForValue = function(obj) { if (obj === null) { return "null"; } else if (typeof obj === "undefined") { return "undefined"; } return obj.toString(); }; var assert = function(args, expectedArgsCount, testFunction, defaultComment) { this.assertCount++; var comment = defaultComment; var i; var success; var values = []; if (args.length == expectedArgsCount) { for (i = 0; i < args.length; i++) { values[i] = args[i]; } } else if (args.length == expectedArgsCount + 1) { comment = args[0]; for (i = 1; i < args.length; i++) { values[i - 1] = args[i]; } } else { throw new Error("Invalid number of arguments passed to assert function"); } success = testFunction(values); if (!success) { var regex = /\{([0-9]+)\}/; while (regex.test(comment)) { comment = comment.replace(regex, displayStringForValue(values[parseInt(RegExp.$1)])); } this.fail("Test failed on assertion " + this.assertCount + ": " + comment); } }; var testNull = function(values) { return (values[0] === null); }; Test.prototype.assertNull = function() { assert.apply(this, [arguments, 1, testNull, "Expected to be null but was {0}"]); } var testNotNull = function(values) { return (values[0] !== null); }; Test.prototype.assertNotNull = function() { assert.apply(this, [arguments, 1, testNotNull, "Expected not to be null but was {0}"]); } var testBoolean = function(values) { return (Boolean(values[0])); }; Test.prototype.assert = function() { assert.apply(this, [arguments, 1, testBoolean, "Expected not to be equivalent to false"]); }; var testTrue = function(values) { return (values[0] === true); }; Test.prototype.assertTrue = function() { assert.apply(this, [arguments, 1, testTrue, "Expected to be true but was {0}"]); }; Test.prototype.assert = function() { assert.apply(this, [arguments, 1, testTrue, "Expected to be true but was {0}"]); }; var testFalse = function(values) { return (values[0] === false); }; Test.prototype.assertFalse = function() { assert.apply(this, [arguments, 1, testFalse, "Expected to be false but was {0}"]); } var testEquivalent = function(values) { return (values[0] === values[1]); }; Test.prototype.assertEquivalent = function() { assert.apply(this, [arguments, 2, testEquivalent, "Expected to be equal but values were {0} and {1}"]); } var testNotEquivalent = function(values) { return (values[0] !== values[1]); }; Test.prototype.assertNotEquivalent = function() { assert.apply(this, [arguments, 2, testNotEquivalent, "Expected to be not equal but values were {0} and {1}"]); } var testEquals = function(values) { return (values[0] == values[1]); }; Test.prototype.assertEquals = function() { assert.apply(this, [arguments, 2, testEquals, "Expected to be equal but values were {0} and {1}"]); } var testNotEquals = function(values) { return (values[0] != values[1]); }; Test.prototype.assertNotEquals = function() { assert.apply(this, [arguments, 2, testNotEquals, "Expected to be not equal but values were {0} and {1}"]); } var testRegexMatches = function(values) { return (values[0].test(values[1])); }; Test.prototype.assertRegexMatches = function() { assert.apply(this, [arguments, 2, testRegexMatches, "Expected regex {0} to match value {1} but it didn't"]); } Test.prototype.assertError = function(f, errorType) { try { f(); this.fail("Expected error to be thrown"); } catch (e) { if (errorType && (!(e instanceof errorType))) { this.fail("Expected error of type " + errorType + " to be thrown but error thrown was " + e); } } }; /** * Execute a synchronous test */ xn.test = function(name, callback) { xn.test.suite("Anonymous", function(s) { s.test(name, callback); }); } /** * Create a test suite with a given name */ xn.test.suite = function(name, callback, hideSuccessful) { var s = new Suite(name, callback, hideSuccessful); } })();