// $Id: /mirror/openjsan/users/theory/Test.Simple/trunk/lib/Test/Harness/Browser.js 1942 2008-08-07T04:29:11.167550Z theory $ /*global JSAN, Test, ActiveXObject */ if (typeof JSAN != 'undefined') JSAN.use('Test.Harness'); else { if (typeof Test == 'undefined') Test = {}; if (!Test.Harness) Test.Harness = {}; } if (window.parent != window && location.href.replace(/[?#].+/, "") == parent.location.href.replace(/[?#].+/, "")) { // Build fake T.H.B so original script from this file doesn't throw // exception. This is a bit of a hack... Test.Harness.Browser = function() { this.runTests = function() {}; this.encoding = function () { return this }; }; // We're in a test iframe. Set up the necessary parts and load the // test script with XMLHttpRequest (to support Safari and Opera). var __MY = {}; __MY.pre = document.createElement("pre"); __MY.pre.id = "test"; if (window.parent.Test.Harness.Browser._encoding) { // Set all scripts to use the appropriate encoding. __MY.scripts = document.getElementsByTagName('script'); for (var j = 0; j < __MY.scripts.length; j++) { __MY.scripts[j].charset = window.parent.Test.Harness.Browser._encoding; } } // XXX replace with a script element at some point? Safari is due to // have this working soon (not sure about IE or Opera): // http://bugs.webkit.org/show_bug.cgi?id=3748 __MY.inc = window.parent.Test.Harness.Browser.includes; __MY.req = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); for (var k = 0; k < __MY.inc.length; k++) { __MY.req.open("GET", __MY.inc[k], false); __MY.req.send(null); var stat = __MY.req.status; // OK Not Modified IE Cached Safari cached if (stat == 200 || stat == 304 || stat == 0 || stat == null) { eval(__MY.req.responseText); } else { throw new Error( "Unable to load " + __MY.inc[k] + ': Status ' + __MY.req.status ); } } // IE 6 SP 2 doesn't seem to run the onload() event, so we force the // issue. Test.Builder._finish(Test); // XXX Opera throws a DOM exception here, but I don't know what to do // about that. __MY.body = document.body || document.getElementsByTagName("body")[0].appendChild(__MY.pre); if (__MY.body) __MY.body.appendChild(__MY.pre); else if (document.appendChild) document.appendChild(__MY.pre); } else { Test.Harness.Browser = function () { this.includes = Test.Harness.Browser.includes = []; Array.prototype.push.apply(Test.Harness.Browser.includes, arguments); this.includes.push(''); }; Test.Harness.Browser.VERSION = '0.26'; Test.Harness.Browser.runTests = function () { var harness = new Test.Harness.Browser(); harness.runTests.apply(harness, arguments); }; Test.Harness.Browser.prototype = new Test.Harness(); Test.Harness.Browser.prototype.interval = 100; Test.Harness.Browser.prototype._setupFrame = function () { // Setup the iFrame to run the tests. var node = document.getElementById('buffer'); if (node) return node.contentWindow || frames.buffer; node = document.createElement("iframe"); node.setAttribute("id", "buffer"); node.setAttribute("name", "buffer"); // Safari makes it impossible to do anything with the iframe if it's // set to display:none. See: // http://www.quirksmode.org/bugreports/archives/2005/02/hidden_iframes.html if (/Safari|Konqueror/.test(navigator.userAgent)) { node.style.visibility = "hidden"; node.style.height = "0"; node.style.width = "0"; } else node.style.display = "none"; document.body.appendChild(node); return node.contentWindow || frames.buffer; }; Test.Harness.Browser.prototype._setupOutput = function () { // Setup the pre element for test output. var node = document.createElement('pre'); node.setAttribute('id', 'output'); document.body.appendChild(node); fixoutput = function(node) { // Trailing space added and replaced to work around yet another // Safari bug. node.innerHTML = node.innerHTML.replace( / ?(\w[\w\.\-]+?\w)(?=\.\.\.)/m, '$1' ) + ' '; }; return { pass: function (msg) { node.appendChild(document.createTextNode(msg)); window.scrollTo(0, document.body.offsetHeight || document.body.scrollHeight); fixoutput(node); }, fail: function (msg) { var red = document.createElement('span'); red.setAttribute('style', 'color: red; font-weight: bold'); node.appendChild(red); red.appendChild(document.createTextNode(msg)); window.scrollTo(0, document.body.offsetHeight || document.body.scrollHeight); } }; }; Test.Harness.Browser.prototype._setupSummary = function () { // Setup the div for the summary. var node = document.createElement("div"); node.setAttribute("id", "summary"); node.setAttribute( "style", "white-space:pre; font-family: Verdana,Arial,serif;" ); document.body.appendChild(node); return function (msg) { node.appendChild(document.createTextNode(msg)); window.scrollTo(0, document.body.offsetHeight || document.body.scrollHeight); }; }; Test.Harness.Browser.prototype.runTests = function () { Test.Harness.Browser._encoding = this.encoding(); var files = this.args.file ? typeof this.args.file == 'string' ? [this.args.file] : this.args.file : arguments; if (!files.length) return; var outfiles = this.outFileNames(files); var buffer = this._setupFrame(); var harness = this; var ti = 0; var start; var output = this._setupOutput(); var summaryOutput = this._setupSummary(); // These depend on how we're watching for a test to finish. var finish = function () {}, runNext = function () {}; // This function handles most of the work of outputting results and // running the next test, if there is one. var runner = function () { harness.outputResults( buffer.Test.Builder.Test, files[ti], output, harness.args ); if (files[++ti]) { output.pass( outfiles[ti] + (harness.args.verbose ? Test.Harness.LF : '') ); harness.runTest(files[ti], buffer); runNext(); } else { harness.outputSummary(summaryOutput, new Date() - start); finish(); } }; // XXX Support IE's propertychange event? // http://msdn.microsoft.com/workshop/author/dhtml/reference/events/onpropertychange.asp if (Object.watch) { // We can use the cool watch method, and avoid setting timeouts! // We just need to unwatch() when all tests are finished. finish = function () { Test.Harness.unwatch('Done') }; Test.Harness.watch('Done', function (attr, prev, next) { if (next < buffer.Test.Builder.Instances.length) return next; runner(); return 0; }); } else { // Damn. We have to set timeouts. :-( var pkg; var wait = function () { // Check Test.Harness.Done. If it's non-zero, then we know // that the buffer is fully loaded, because it has incremented // Test.Harness.Done. Grrr.. IE 6 SP 2 seems to delete // buffer.Test after all the tests have finished running, but // before this code executes for the correct number of // completed tests. So we cache it in a variable outside of // the function on previous calls to the function. if (!pkg) pkg = buffer.Test; if (Test.Harness.Done > 0 && Test.Harness.Done >= pkg.Builder.Instances.length) { Test.Harness.Done = 0; // Avoid race condition by resetting the instances, too. I // have no idea why this might remain set from a previous // test, but such can be the case in IE 6 SP 2. pkg.Builder.Instances = []; runner(); } else { window.setTimeout(wait, harness.interval); } }; // We'll just have to set a timeout for the next test. runNext = function () { window.setTimeout(wait, harness.interval); }; window.setTimeout(wait, this.interval); } // Now start the first test. output.pass(outfiles[ti] + (this.args.verbose ? Test.Harness.LF : '')); start = new Date(); this.runTest(files[ti], buffer); }; Test.Harness.Browser.prototype.runTest = function (file, buffer) { if (/\.html$/.test(file)) { buffer.location.replace(file); } else { // if (/\.js$/.test(file)) { if (/MSIE|Opera|Safari|Konqueror/.test(navigator.userAgent)) { // These browsers have problems with the DOM solution. It // simply doesn't work in Safari, and Opera considers its // handling of buffer.document to be a security violation. So // have them use the XML hack, instead. this.includes[this.includes.length-1] = file; buffer.location.replace(location.pathname + "?xml-hack=1"); return; } // document.write() simply doesn't work here. Thanks to // Pawel Chmielowski for figuring that out! var doc = buffer.document; doc.open("text/html"); doc.close(); var el; // XXX Opera chokes on this line. It thinks that using the doc // element like this is a security violation, never mind that we // were the ones who actually created it. Whatever! var body = doc.body || doc.getElementsByTagName("body")[0]; var head = doc.getElementsByTagName("head")[0]; // Safari seems to be headless at this point. if (!head) { head = doc.createElement('head'); doc.appendChild(head); } // Add script elements for all includes. for (var i = 0; i < this.includes.length - 1; i++) { el = doc.createElement("script"); el.setAttribute("src", this.includes[i]); head.appendChild(el); } // Create the pre and script element for the test file. var pre = doc.createElement("pre"); pre.id = "test"; el = doc.createElement("script"); el.type = "text/javascript"; if (this.encoding()) el.charset = this.encoding(); // XXX This doesn't work in Safari right now. See // http://bugs.webkit.org/show_bug.cgi?id=3748 el.src = file; pre.appendChild(el); // Create a script element to finish the tests. el = doc.createElement("script"); el.type = "text/javascript"; var text = "window.onload(null, Test)"; // IE doesn't let script elements have children. if (null != el.canHaveChildren) el.text = text; // But most other browsers do. else el.appendChild(document.createTextNode(text)); pre.appendChild(el); // IE 6 SP 2 Requires getting the body element again. body = doc.body || doc.getElementsByTagName("body")[0]; body.appendChild(pre); /* Let's just assume that if it's not .html, it's JavaScript. } else { // Who are you, man?? alert("I don't know what kind of file '" + file + "' is"); */ } }; Test.Harness.Browser.prototype.args = {}; var pairs = location.search.substring(1).split(/[;&]/); for (var i = 0; i < pairs.length; i++) { var parts = pairs[i].split('='); if (parts[0] == null) continue; var key = unescape(parts[0]), val = unescape(parts[1]); if (Test.Harness.Browser.prototype.args[key] == null) { Test.Harness.Browser.prototype.args[key] = unescape(val); } else { if (typeof Test.Harness.Browser.prototype.args[key] == 'string') { Test.Harness.Browser.prototype.args[key] = [Test.Harness.Browser.prototype.args[key], unescape(val)]; } else { Test.Harness.Browser.prototype.args[key].push(unescape(val)); } } } Test.Harness.Browser.prototype.formatFailures = function (fn) { // XXX Switch to DOM? var failedStr = "Failed Test"; var middleStr = " Total Fail Failed "; var listStr = "List of Failed"; var table = '' + '' + ''; for (var i = 0; i < this.failures.length; i++) { var track = this.failures[i]; var style = i % 2 ? 'even' : 'odd'; table += '' + '' + '' + ''; }; table += '
Failed TestTotalFailFailed
' + track.fn + '' + track.total + '' + (track.total - track.ok) + '' + this._failList(track.failList) + '
' + Test.Harness.LF; var node = document.getElementById('summary'); node.innerHTML += table; window.scrollTo(0, document.body.offsetHeight || document.body.scrollHeight); }; }