/*
* jsunit.js
*
* Authors:
* Chris Toshok (toshok@ximian.com)
*
* (c) 2005 Novell, Inc. (http://www.novell.com)
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* For the time being, this is a total hack, precariously balanced and
* ready to tip over if you look at it wrong. don't look at it
* wrong. */
var debugging = false;
/* A trace object, that creates a console-esque window and lets us put debugging info there */
Trace = function() { this.Init(); };
Trace.prototype = {
Init: function () {
this.w = null;
},
ensure_window: function () {
if (!this.w)
this.w = window.open ("", "trace window", "height=300,width=400");
this.w.focus();
this.w.document.write ("
");
},
debug: function (msg){
if (!debugging) return;
this.ensure_window ();
this.w.document.write ( "" + msg + "
" );
}
};
/* An Assert object, to make our tests look a little more like nunit's */
var Assert = {
IsTrue: function(expr, msg) {
try {
var result = eval(expr);
if (result)
test_passed (msg);
else
test_failed (msg, expr);
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
},
IsFalse: function(expr, msg) {
try {
var result = eval(expr);
if (!result)
test_passed (msg);
else
test_failed (msg, expr);
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
},
IsNull: function(expr, msg) {
try {
var result = eval(expr);
if (result == null)
test_passed (msg);
else
test_failed (msg, "'" + encode (expr) + "' returned non-null value '" + result + "'");
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
},
NotNull: function(expr, msg) {
try {
var result = eval(expr);
if (result != null)
test_passed (msg);
else
test_failed (msg, "'" + encode (expr) + "' returned null");
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
},
AreEqual: function(expected, expr, msg) {
try {
var result = eval(expr);
/* gross hack because mozilla collapses these down to \n, but IE doesn't */
result = string_trim (result.replace (/\r\n/g, "\n"));
expected = string_trim (expected.replace (/\r\n/g, "\n"));
if (result == expected)
test_passed (msg);
else
test_failed (msg, "expected (len = " + expected.length + ") <" + encode (expected) + ">, got (len = " + result.length + " ) <" + encode (result) + "> " + string_charcode_diff(expected, result));
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
},
AreEqualCase: function(expected, expr, msg) {
try {
var result = eval(expr);
/* gross hack because mozilla collapses these down to \n, but IE doesn't */
result = result.replace (/\r\n/g, "\n");
expected = expected.replace (/\r\n/g, "\n");
if (string_trim (result.toLowerCase()) == string_trim (expected.toLowerCase()))
test_passed (msg);
else
test_failed (msg, "expected (len = " + expected.length + ") <" + encode (expected.toLowerCase()) + ">, got (len = " + result.length + " ) <" + encode (result.toLowerCase()) + "> " + string_charcode_diff(expected.toLowerCase(), result.toLowerCase()));
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
},
NotEqual: function(expected, expr, msg) {
try {
var result = eval(expr);
if (result != expected)
test_passed (msg);
else
test_failed (msg, expr);
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
},
Contains: function(expected, expr, msg) {
try {
var result = eval(expr);
if (result.indexOf (expected) != -1)
test_passed (msg);
else
test_failed (msg, expr);
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
},
IsFunction: function(expr, msg) {
try {
var result = eval(expr);
if (typeof (result) == 'function')
test_passed (msg);
else
test_failed (msg, "expected <function>, got <" + typeof (result) + ">");
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
},
AttributeHasValue: function (expected, attr, msg) {
try {
var result = JSUnit_GetAttribute (attr);
if (result == expected)
test_passed (msg);
else
test_failed (msg, "expected <" + encode (expected) + ">, got <" + (result == null ? "null" : encode (result)) + ">");
} catch (e) {
test_failed (msg, "Exception: " + e.message);
}
}
};
/* helper functions for tests */
var element_name;
var bound_element;
var test_causes_page_load = false;
function JSUnit_BindElement (n)
{
element_name = n;
bound_element = top.test_run.document.getElementById (element_name);
}
function JSUnit_GetElement(id)
{
if (typeof (id) == 'undefined')
return bound_element;
else
return top.test_run.document.getElementById (id);
}
function JSUnit_GetAttribute(a, id)
{
var o = JSUnit_GetElement (id);
if (o == null)
return null;
if (o[a])
return o[a];
else if (o.getAttribute)
return o.getAttribute (a);
else
return null;
}
function JSUnit_TestCausesPageLoad ()
{
trace.debug ("in JSUnit_TestCausesPageLoad");
test_causes_page_load = true;
}
function JSUnit_Click (el)
{
trace.debug ("in JSUnit_Click");
if (el == null) {
trace.debug (" + returning early, element == null");
return;
}
if (test_causes_page_load && !use_onload) {
top.test_run.waiting = true;
trace.debug ("adding checkReadState timeout");
setTimeout ("checkReadyState()", 100);
}
if (el.click) {
trace.debug ("+ using el.click()");
el.click();
}
else if (el.getAttribute ("onClick")) {
trace.debug ("+ using onClick handler");
var handler = new Function (el.getAttribute ("onClick"));
var evt = top.test_run.document.createEvent ("MouseEvents");
evt.initEvent ("click", true, true);
handler.call (el, evt);
}
else if (el.getAttribute ("href")) {
var content_window = JSUnit_GetContentWindow (top.test_run.frame);
trace.debug ("+ setting test_run src = " + el.getAttribute ("href") + " from " + content_window.location.href);
content_window.location.href = el.getAttribute ("href");
}
else {
alert ("uh oh...");
}
}
function JSUnit_ExpectFailure(msg)
{
next_test_expected_failure = true;
expected_failure_msg = msg;
}
/* the machinery */
var trace = new Trace();
var use_onload = true;
var test_run_loaded = false;
var test_scripts_loaded = false;
var current_testpage = -1;
var current_test = -1;
var current_tests;
var next_test_expected_failure;
var expected_failure_msg;
var current_test_html;
var total_expected_failures = 0;
var total_failed = 0;
var total_tests = 0;
var result_div;
top.test_run = new Object();
top.test_scripts = new Object();
top.test_results = new Object();
function updateStatusText (str)
{
status_text.innerHTML = "(" + str + ", " + parseInt (((current_testpage + 0.0) / (JSUnit_TestPages.length + 0.0) * 100) + "") + "% completed)";
}
function JSUnit_OnLoad ()
{
trace.debug ("in JSUnit_Onload");
top.test_run.frame = document.getElementById ("test-run");
top.test_scripts.frame = document.getElementById ("test-scripts");
top.test_results.frame = document.getElementById ("test-results");
top.test_results.document = JSUnit_GetContentDocument (top.test_results.frame);
status_text = top.test_results.document.getElementById ("status_text");
result_div = top.test_results.document.getElementById ("JSUnit_Results");
if (result_div == null || typeof (result_div) == 'undefined') {
alert ("Couldn't find result div");
return;
}
result_div.innerHTML = "";
if (navigator.userAgent.indexOf("MSIE") != -1) {
use_onload = false;
}
else {
top.test_run.frame.onload = test_run_onload;
top.test_scripts.frame.onload = test_scripts_onload;
use_onload = true;
}
/* set up the html for the list of pages we're testing */
for (var i in JSUnit_TestPages) {
html = "
";
html += " ";
html += "+" + "" + JSUnit_TestPages[i].url + ":
\n";
html += "\n";
result_div.innerHTML += html;
}
for (var i in JSUnit_TestPages) {
JSUnit_TestPages[i].results_div = top.test_results.document.getElementById ("results-" + JSUnit_TestPages[i].url);
JSUnit_TestPages[i].indicator = top.test_results.document.getElementById ("indicator-" + JSUnit_TestPages[i].url);
JSUnit_TestPages[i].failures_span = top.test_results.document.getElementById ("failures-" + JSUnit_TestPages[i].url);
JSUnit_TestPages[i].spinner = top.test_results.document.getElementById ("spinner-" + JSUnit_TestPages[i].url);
}
jsunit_RunTestPageStep();
}
var query_string_hack = 0;
function checkReadyState ()
{
var need_timeout = false;
if ((top.test_run.waiting && JSUnit_GetContentDocument(top.test_run.frame).readyState != "complete")
|| (top.test_scripts.waiting && JSUnit_GetContentDocument(top.test_scripts.frame).readyState != "complete")) {
setTimeout("checkReadyState()", 100);
}
if (top.test_run.waiting) {
if (JSUnit_GetContentDocument(top.test_run.frame).readyState == "complete") {
top.test_run.waiting = false;
test_run_onload();
}
}
if (top.test_scripts.waiting) {
if (JSUnit_GetContentDocument(top.test_scripts.frame).readyState == "complete") {
top.test_run.waiting = false;
test_scripts_onload();
}
}
}
function jsunit_RunTestPageStep ()
{
/* first hide the spinner from the old test, if there was one */
if (current_testpage >= 0) {
JSUnit_TestPages[current_testpage].spinner.style.visibility="hidden";
}
current_testpage ++;
if (current_testpage >= JSUnit_TestPages.length) {
jsunit_TestsCompleted();
return;
}
status_text.style.display="inline";
updateStatusText ("loading " + JSUnit_TestPages[current_testpage].url);
JSUnit_TestPages[current_testpage].spinner.style.visibility = "visible";
top.test_run.loaded = false;
top.test_scripts.loaded = false;
top.test_run.waiting = true;
if (JSUnit_TestPages[current_testpage].script) {
top.test_scripts.waiting = true;
}
else {
top.test_scripts.waiting = false;
}
if (!use_onload) {
setTimeout ("checkReadyState()", 100);
}
top.test_run.frame.src = "";
top.test_scripts.frame.src = "";
/* start the page loading */
cw = JSUnit_GetContentWindow (top.test_run.frame);
cw.location.href = JSUnit_TestPages[current_testpage].url + "?" + query_string_hack;
/* start the script loading, if there is one */
if (JSUnit_TestPages[current_testpage].script) {
cw = JSUnit_GetContentWindow (top.test_scripts.frame);
cw.location.href = JSUnit_TestPages[current_testpage].script + "?" + query_string_hack;
query_string_hack++;
}
}
function jsunit_TestsCompleted ()
{
trace.debug ("in jsunit_TestsCompleted");
status_text.style.display="none";
result_div.innerHTML += "
Totals: " + total_tests + " tests, " + total_failed + " failure" + (total_failed != 1 ? "s" : "");
if (total_expected_failures > 0) {
result_div.innerHTML += " (" + total_expected_failures + " expected)";
}
result_div.innerHTML += ".";
}
function jsunit_FindTestFixture ()
{
var script_context = JSUnit_GetContentWindow (top.test_run.frame);
if (!script_context['TestFixture']) {
script_context = JSUnit_GetContentWindow (top.test_scripts.frame);
if (!script_context['TestFixture'])
return;
}
top.test_fixture = script_context['TestFixture'];
top.test_fixture_context = script_context;
}
function jsunit_RunTestsForPage ()
{
trace.debug ("in jsunit_RunTestsForPage");
// XXX for now, disable
// netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
jsunit_FindTestFixture ();
current_test = -1;
current_tests = new Array();
for (var t in top.test_fixture)
current_tests.push (t);
updateStatusText ("testing " + JSUnit_TestPages[current_testpage].url);
page_total_tests = 0;
page_total_failed = 0;
page_total_expected_failures = 0;
current_test_html = "";
JSUnit_TestPages[current_testpage].failures_span.innerHTML = "0 tests";
jsunit_RunTestForPageStep ();
}
function jsunit_RunTestForPageStep ()
{
trace.debug ("in jsunit_RunTestForPageStep");
// XXX for now, disable
// netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
// need this in case the test fixture was embedded in the test-run
// page.
jsunit_FindTestFixture ();
// add the public api if it's not already there for the page
if (!top.test_fixture_context['Assert'])
jsunit_AddPublicAPI (top.test_fixture_context);
current_test ++;
if (current_test < current_tests.length) {
var testfunc = top.test_fixture[current_tests[current_test]];
if (typeof (testfunc) == 'function') {
current_test_html += current_tests[current_test] + "\n";
try {
trace.debug ("invoking test: " + current_tests[current_test]);
testfunc ();
} catch (e) {
test_failed ("test function error", "Exception: " + e.message);
}
current_test_html += "
";
}
update_failures_span(false);
JSUnit_TestPages[current_testpage].results_div.innerHTML = current_test_html;
if (test_causes_page_load)
return;
else
jsunit_RunTestForPageStep ();
}
else {
if (page_total_failed > 0)
current_test_html += "test html
";
JSUnit_TestPages[current_testpage].results_div.innerHTML = current_test_html;
update_failures_span (true);
/* once we're done with this page, advance to the next */
jsunit_RunTestPageStep ();
}
}
function jsunit_AddPublicAPI (ctx)
{
ctx['JSUnit_ExpectFailure'] = JSUnit_ExpectFailure;
ctx['JSUnit_Click'] = JSUnit_Click;
ctx['JSUnit_TestCausesPageLoad'] = JSUnit_TestCausesPageLoad;
ctx['JSUnit_BindElement'] = JSUnit_BindElement;
ctx['JSUnit_GetElement'] = JSUnit_GetElement;
ctx['JSUnit_GetAttribute'] = JSUnit_GetAttribute;
ctx['Assert'] = Assert;
ctx['Trace'] = trace;
}
function update_failures_span (finished)
{
JSUnit_TestPages[current_testpage].failures_span.innerHTML = page_total_tests + " tests, " + page_total_failed + " failure" + (total_failed != 1 ? "s" : "");
if (page_total_expected_failures > 0) {
JSUnit_TestPages[current_testpage].failures_span.innerHTML += " (" + page_total_expected_failures + " expected)";
}
if (finished) {
/* update the color to either green or red */
JSUnit_TestPages[current_testpage].failures_span.style.background = (page_total_failed - page_total_expected_failures > 0) ? "#ff0000" : "#00ff00";
}
else {
/* update the color to red if there's been a failure */
if (page_total_failed - page_total_expected_failures > 0)
JSUnit_TestPages[current_testpage].failures_span.style.background = "#ff0000";
}
}
function test_run_onload ()
{
trace.debug ("in test_run_onload");
top.test_run.document = JSUnit_GetContentDocument (top.test_run.frame);
if (test_causes_page_load) {
trace.debug ("+ resetting causes_page_load");
test_causes_page_load = false;
jsunit_RunTestForPageStep ();
return;
}
top.test_run.loaded = true;
if ((!top.test_scripts.waiting || top.test_scripts.loaded)) {
trace.debug ("+ starting tests for page");
/* both the page and its script have been loaded, let's run them */
jsunit_RunTestsForPage ();
}
}
function test_scripts_onload ()
{
trace.debug ("in test_scripts_onload");
top.test_scripts.document = JSUnit_GetContentDocument (top.test_scripts.frame);
top.test_scripts.loaded = true;
if (top.test_run.loaded) {
trace.debug ("+ starting tests for page");
/* both the page and its script have been loaded, let's run them */
jsunit_RunTestsForPage ();
}
}
/* utility functions */
function JSUnit_GetContentDocument (frame)
{
if (frame.contentDocument != null)
return frame.contentDocument;
else
return frame.contentWindow.document;
}
function JSUnit_GetContentWindow (frame)
{
try {
if (frame.contentDocument && frame.contentDocument.defaultView)
return frame.contentDocument.defaultView;
else
return frame.contentWindow;
} catch (e) {
trace.debug ("exception getting content window: " + e);
return null;
}
}
function test_passed (msg)
{
next_test_expected_failure = false;
current_test_html += "| " + msg + " | PASSED |
";
page_total_tests ++;
total_tests ++;
}
function test_failed (msg, extra)
{
if (next_test_expected_failure) {
extra += "
Expected failure: " + expected_failure_msg;
next_test_expected_failure = false;
page_total_expected_failures ++;
total_expected_failures ++;
}
current_test_html += "| " + msg + " | FAILED " + extra + " |
";
page_total_tests ++;
page_total_failed ++;
total_tests ++;
total_failed ++;
}
function update_failures_span (finished)
{
JSUnit_TestPages[current_testpage].failures_span.innerHTML = page_total_tests + " tests, " + page_total_failed + " failure" + (total_failed != 1 ? "s" : "");
if (page_total_expected_failures > 0) {
JSUnit_TestPages[current_testpage].failures_span.innerHTML += " (" + page_total_expected_failures + " expected)";
}
if (finished) {
/* update the color to either green or red */
JSUnit_TestPages[current_testpage].failures_span.style.background = (page_total_failed - page_total_expected_failures > 0) ? "#ff0000" : "#00ff00";
}
else {
/* update the color to red if there's been a failure */
if (page_total_failed - page_total_expected_failures > 0)
JSUnit_TestPages[current_testpage].failures_span.style.background = "#ff0000";
}
}
function encode (str)
{
return str.replace (//g, ">").replace (/\n/g, "\\n");
}
function string_charcode_diff (str1, str2)
{
var length = str1.length;
if (str2.length < str1.length)
length = str2.length;
for (i = 0; i < length; i ++) {
var code1 = str1.charCodeAt (i);
var code2 = str2.charCodeAt (i);
if (code1 != code2)
return "index = " + i + ", str1 = " + code1 + ", str2 = " + code2;
}
return "";
}
function string_trim (s)
{
s = s.replace (/^\s+/g, "");
s = s.replace (/\s+$/g, "");
return s;
}