797 lines
24 KiB
JavaScript
797 lines
24 KiB
JavaScript
// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
|
|
|
|
// Set up namespace.
|
|
if (typeof self != 'undefined') {
|
|
// Browser
|
|
if (typeof Test == 'undefined') Test = {PLATFORM: 'browser'};
|
|
else Test.PLATFORM = 'browser';
|
|
} else if (typeof _global != 'undefined') {
|
|
//Director
|
|
if (typeof _global.Test == "undefined") _global.Test = {PLATFORM: 'director'};
|
|
else _global.Test.PLATFORM = 'director';
|
|
} else {
|
|
throw new Error("Test.More does not support your platform");
|
|
}
|
|
|
|
// Constructor.
|
|
Test.Builder = function () {
|
|
if (!Test.Builder.Test) {
|
|
Test.Builder.Test = this.reset();
|
|
Test.Builder.Instances.push(this);
|
|
}
|
|
return Test.Builder.Test;
|
|
};
|
|
|
|
// Static variables.
|
|
Test.Builder.VERSION = '0.11';
|
|
Test.Builder.Instances = [];
|
|
Test.Builder.lineEndingRx = /\r?\n|\r/g;
|
|
Test.Builder.StringOps = {
|
|
eq: '==',
|
|
ne: '!=',
|
|
lt: '<',
|
|
gt: '>',
|
|
ge: '>=',
|
|
le: '<='
|
|
};
|
|
|
|
// Stoopid IE.
|
|
Test.Builder.LF = typeof document != "undefined"
|
|
&& typeof document.all != "undefined"
|
|
? "\r"
|
|
: "\n";
|
|
|
|
// Static methods.
|
|
Test.Builder.die = function (msg) {
|
|
throw new Error(msg);
|
|
};
|
|
|
|
Test.Builder._whoa = function (check, desc) {
|
|
if (!check) return;
|
|
Test.Builder.die("WHOA! " + desc + Test.Builder.LF +
|
|
+ "This should never happen! Please contact the author "
|
|
+ "immediately!");
|
|
};
|
|
|
|
Test.Builder.typeOf = function (object) {
|
|
var c = Object.prototype.toString.apply(object);
|
|
var name = c.substring(8, c.length - 1);
|
|
if (name != 'Object') return name;
|
|
// It may be a non-core class. Try to extract the class name from
|
|
// the constructor function. This may not work in all implementations.
|
|
if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) {
|
|
return RegExp.$1;
|
|
}
|
|
// No idea. :-(
|
|
return name;
|
|
};
|
|
|
|
// Instance methods.
|
|
Test.Builder.create = function () {
|
|
var test = Test.Builder.Test;
|
|
Test.Builder.Test = null;
|
|
var ret = new Test.Builder();
|
|
Test.Builder.Test = test;
|
|
return ret.reset();
|
|
};
|
|
|
|
Test.Builder.prototype.reset = function () {
|
|
this.TestDied = false;
|
|
this.HavePlan = false;
|
|
this.NoPlan = false;
|
|
this.CurrTest = 0;
|
|
this.ExpectedTests = 0;
|
|
this.UseNums = true;
|
|
this.NoHeader = false;
|
|
this.NoEnding = false;
|
|
this.TestResults = [];
|
|
this.ToDo = [];
|
|
this.Buffer = [];
|
|
this.asyncs = [0];
|
|
this.asyncID = 0;
|
|
return this._setupOutput();
|
|
};
|
|
|
|
Test.Builder.prototype._print = function (msg) {
|
|
this.output().call(this, msg);
|
|
};
|
|
|
|
Test.Builder.prototype.warn = function (msg) {
|
|
this.warnOutput().apply(this, arguments);
|
|
};
|
|
|
|
Test.Builder.prototype.plan = function (arg) {
|
|
if (!arg) return;
|
|
//if (this.HavePlan) Test.Builder.die("You tried to plan twice!");
|
|
this.ExpectedTests = 0;
|
|
this.HavePlan = false;
|
|
this.NoPlan = false;
|
|
|
|
if (!(arg instanceof Object))
|
|
Test.Builder.die("plan() doesn't understand " + arg);
|
|
for (var cmd in arg) {
|
|
if (cmd == 'tests') {
|
|
if (arg[cmd] == null) {
|
|
TestBulder.die(
|
|
"Got an undefined number of tests. Looks like you tried to "
|
|
+ "say how many tests you plan to run but made a mistake."
|
|
+ Test.Builder.LF
|
|
);
|
|
} else if (!arg[cmd]) {
|
|
Test.Builder.die(
|
|
"You said to run 0 tests! You've got to run something."
|
|
+ Test.Builder.LF
|
|
);
|
|
} else {
|
|
this.expectedTests(arg[cmd]);
|
|
}
|
|
} else if (cmd == 'skipAll') {
|
|
this.skipAll(arg[cmd]);
|
|
} else if (cmd == 'noPlan' && arg[cmd]) {
|
|
this.noPlan();
|
|
} else {
|
|
Test.Builder.die("plan() doesn't understand "
|
|
+ cmd + (arg[cmd] ? (" " + arg[cmd]) : ''));
|
|
}
|
|
}
|
|
};
|
|
|
|
Test.Builder.prototype.expectedTests = function (max) {
|
|
if (max) {
|
|
if (isNaN(max)) {
|
|
Test.Builder.die(
|
|
"Number of tests must be a postive integer. You gave it '"
|
|
+ max + "'." + Test.Builder.LF
|
|
);
|
|
}
|
|
|
|
this.ExpectedTests = max.valueOf();
|
|
this.HavePlan = 1;
|
|
if (!this.noHeader()) this._print("1.." + max + Test.Builder.LF);
|
|
}
|
|
return this.ExpectedTests;
|
|
};
|
|
|
|
Test.Builder.prototype.noPlan = function () {
|
|
this.NoPlan = 1;
|
|
this.HavePlan = 1;
|
|
};
|
|
|
|
Test.Builder.prototype.hasPlan = function () {
|
|
if (this.ExpectedTests) return this.ExpectedTests;
|
|
if (this.NoPlan) return 'noPlan';
|
|
};
|
|
|
|
Test.Builder.prototype.skipAll = function (reason) {
|
|
var out = "1..0";
|
|
if (reason) out += " # Skip " + reason;
|
|
out += Test.Builder.LF;
|
|
this.SkipAll = 1;
|
|
if (!this.noHeader()) this._print(out);
|
|
// Just throw and catch an exception.
|
|
window.onerror = function () { return true; }
|
|
throw new Error("__SKIP_ALL__");
|
|
};
|
|
|
|
Test.Builder.prototype.ok = function (test, desc) {
|
|
// test might contain an object that we don't want to accidentally
|
|
// store, so we turn it into a boolean.
|
|
test = !!test;
|
|
|
|
if (!this.HavePlan)
|
|
Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
|
|
|
|
// I don't think we need to worry about threading in JavaScript.
|
|
this.CurrTest++;
|
|
|
|
// In case desc is a string overloaded object, force it to stringify.
|
|
if (desc) desc = desc.toString();
|
|
|
|
var startsNumber
|
|
if (desc != null && /^[\d\s]+$/.test(desc)) {
|
|
this.diag( "Your test description is '" + desc + "'. You shouldn't use",
|
|
Test.Builder.LF,
|
|
"numbers for your test names. Very confusing.");
|
|
}
|
|
|
|
var todo = this._todo();
|
|
// I don't think we need to worry about result beeing shared between
|
|
// threads.
|
|
var out = '';
|
|
var result = {};
|
|
|
|
if (test) {
|
|
result.ok = true;
|
|
result.actual_ok = test;
|
|
} else {
|
|
out += 'not ';
|
|
result.ok = todo ? true : false;
|
|
result.actual_ok = false;
|
|
}
|
|
|
|
out += 'ok';
|
|
if (this.useNumbers) out += ' ' + this.CurrTest;
|
|
|
|
if (desc == null) {
|
|
result.desc = '';
|
|
} else {
|
|
desc = desc.replace(Test.Builder.lineEndingRx, Test.Builder.LF + "# ");
|
|
// XXX Does this matter since we don't have a TestHarness?
|
|
desc.split('#').join('\\#'); // # # in a desc can confuse TestHarness.
|
|
out += ' - ' + desc;
|
|
result.desc = desc;
|
|
}
|
|
|
|
if (todo) {
|
|
todo = todo.replace(Test.Builder.lineEndingRx, Test.Builder.LF + "# ");
|
|
out += " # TODO " + todo;
|
|
result.reason = todo;
|
|
result.type = 'todo';
|
|
} else {
|
|
result.reason = '';
|
|
result.type = '';
|
|
}
|
|
|
|
this.TestResults[this.CurrTest - 1] = result;
|
|
|
|
out += Test.Builder.LF;
|
|
this._print(out);
|
|
|
|
if (!test) {
|
|
var msg = todo ? "Failed (TODO)" : "Failed";
|
|
// XXX Hrm, do I need this?
|
|
//$self_print_diag(Test.Builder.LF) if $ENV{HARNESS_ACTIVE};
|
|
this.diag(" " + msg + " test");
|
|
}
|
|
result.output = this.Buffer.splice(0).join('');
|
|
return test;
|
|
};
|
|
|
|
Test.Builder.prototype.isEq = function (got, expect, desc) {
|
|
if (got == null || expect == null) {
|
|
// undefined only matches undefined and nothing else
|
|
return this.isUndef(got, '==', expect, desc);
|
|
}
|
|
return this.cmpOK(got, '==', expect, desc);
|
|
};
|
|
|
|
Test.Builder.prototype.isNum = function (got, expect, desc) {
|
|
if (got == null || expect == null) {
|
|
// undefined only matches undefined and nothing else
|
|
return this.isUndef(got, '==', expect, desc);
|
|
}
|
|
return this.cmpOK(Number(got), '==', Number(expect), desc);
|
|
};
|
|
|
|
Test.Builder.prototype.isntEq = function (got, dontExpect, desc) {
|
|
if (got == null || dontExpect == null) {
|
|
// undefined only matches undefined and nothing else
|
|
return this.isUndef(got, '!=', dontExpect, desc);
|
|
}
|
|
return this.cmpOK(got, '!=', dontExpect, desc);
|
|
};
|
|
|
|
Test.Builder.prototype.isntNum = function (got, dontExpect, desc) {
|
|
if (got == null || dontExpect == null) {
|
|
// undefined only matches undefined and nothing else
|
|
return this.isUndef(got, '!=', dontExpect, desc);
|
|
}
|
|
return this.cmpOK(Number(got), '!=', Number(dontExpect), desc);
|
|
};
|
|
|
|
Test.Builder.prototype.like = function (val, regex, desc) {
|
|
return this._regexOK(val, regex, '=~', desc);
|
|
};
|
|
|
|
Test.Builder.prototype.unlike = function (val, regex, desc) {
|
|
return this._regexOK(val, regex, '!~', desc);
|
|
};
|
|
|
|
Test.Builder.prototype._regexOK = function (val, regex, cmp, desc) {
|
|
// Create a regex object.
|
|
var type = Test.Builder.typeOf(regex);
|
|
var ok;
|
|
if (type.toLowerCase() == 'string') {
|
|
// Create a regex object.
|
|
regex = new RegExp(regex);
|
|
} else {
|
|
if (type != 'RegExp') {
|
|
ok = this.ok(false, desc);
|
|
this.diag("'" + regex + "' doesn't look much like a regex to me.");
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
if (val == null || typeof val != 'string') {
|
|
if (cmp == '=~') {
|
|
// The test fails.
|
|
ok = this.ok(false, desc);
|
|
this._diagLike(val, regex, cmp);
|
|
} else {
|
|
// undefined matches nothing (unlike in Perl, where undef =~ //).
|
|
ok = this.ok(true, desc);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// Use val.match() instead of regex.test() in case they've set g.
|
|
var test = val.match(regex);
|
|
if (cmp == '!~') test = !test;
|
|
ok = this.ok(test, desc);
|
|
if (!ok) this._diagLike(val, regex, cmp);
|
|
return ok;
|
|
};
|
|
|
|
Test.Builder.prototype._diagLike = function (val, regex, cmp) {
|
|
var match = cmp == '=~' ? "doesn't match" : " matches";
|
|
return this.diag(
|
|
" '" + val + "" + Test.Builder.LF +
|
|
" " + match + " /" + regex.source + "/"
|
|
);
|
|
};
|
|
|
|
Test.Builder.prototype.cmpOK = function (got, op, expect, desc) {
|
|
|
|
var test;
|
|
if (Test.Builder.StringOps[op]) {
|
|
// Force string context.
|
|
test = eval("got.toString() " + Test.Builder.StringOps[op] + " expect.toString()");
|
|
} else {
|
|
test = eval("got " + op + " expect");
|
|
}
|
|
|
|
var ok = this.ok(test, desc);
|
|
if (!ok) {
|
|
if (/^(eq|==)$/.test(op)) {
|
|
this._isDiag(got, op, expect);
|
|
} else {
|
|
this._cmpDiag(got, op, expect);
|
|
}
|
|
}
|
|
return ok;
|
|
};
|
|
|
|
Test.Builder.prototype._cmpDiag = function (got, op, expect) {
|
|
if (got != null) got = "'" + got.toString() + "'";
|
|
if (expect != null) expect = "'" + expect.toString() + "'";
|
|
return this.diag(" " + got + Test.Builder.LF + " " + op
|
|
+ Test.Builder.LF + " " + expect);
|
|
};
|
|
|
|
Test.Builder.prototype._isDiag = function (got, op, expect) {
|
|
var args = [got, expect];
|
|
for (var i = 0; i < args.length; i++) {
|
|
if (args[i] != null) {
|
|
args[i] = op == 'eq' ? "'" + args[i].toString() + "'" : args[i].valueOf();
|
|
}
|
|
}
|
|
|
|
return this.diag(
|
|
" got: " + args[0] + Test.Builder.LF +
|
|
" expected: " + args[1] + Test.Builder.LF
|
|
);
|
|
};
|
|
|
|
Test.Builder.prototype.BAILOUT = function (reason) {
|
|
this._print("Bail out! " + reason);
|
|
// Just throw and catch an exception.
|
|
window.onerror = function () {
|
|
// XXX Do something to tell TestHarness it was a bailout?
|
|
return true;
|
|
}
|
|
throw new Error("__BAILOUT__");
|
|
};
|
|
|
|
Test.Builder.prototype.skip = function (why) {
|
|
if (!this.HavePlan)
|
|
Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
|
|
|
|
// In case desc is a string overloaded object, force it to stringify.
|
|
if (why) why = why.toString().replace(Test.Builder.lineEndingRx,
|
|
Test.Builder.LF+ "# ");
|
|
|
|
this.CurrTest++;
|
|
this.TestResults[this.CurrTest - 1] = {
|
|
ok: true,
|
|
actual_ok: true,
|
|
desc: '',
|
|
type: 'skip',
|
|
reason: why
|
|
};
|
|
|
|
var out = "ok";
|
|
if (this.useNumbers) out += ' ' + this.CurrTest;
|
|
out += " # skip " + why + Test.Builder.LF;
|
|
this._print(out);
|
|
this.TestResults[this.CurrTest - 1].output =
|
|
this.Buffer.splice(0).join('');
|
|
return true;
|
|
};
|
|
|
|
Test.Builder.prototype.todoSkip = function (why) {
|
|
if (!this.HavePlan)
|
|
Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
|
|
|
|
// In case desc is a string overloaded object, force it to stringify.
|
|
if (why) why = why.toString().replace(Test.Builder.lineEndingRx,
|
|
Test.Builder.LF + "# ");
|
|
|
|
|
|
this.CurrTest++;
|
|
this.TestResults[this.CurrTest - 1] = {
|
|
ok: true,
|
|
actual_ok: false,
|
|
desc: '',
|
|
type: 'todo_skip',
|
|
reason: why
|
|
};
|
|
|
|
var out = "not ok";
|
|
if (this.useNumbers) out += ' ' + this.CurrTest;
|
|
out += " # TODO & SKIP " + why + Test.Builder.LF;
|
|
this._print(out);
|
|
this.TestResults[this.CurrTest - 1].output =
|
|
this.Buffer.splice(0).join('');
|
|
return true;
|
|
};
|
|
|
|
Test.Builder.prototype.skipRest = function (reason) {
|
|
var out = "# Skip";
|
|
if (reason) out += " " + reason;
|
|
out += Test.Builder.LF;
|
|
if (this.NoPlan) this.skip(reason);
|
|
else {
|
|
for (var i = this.CurrTest; i < this.ExpectedTests; i++) {
|
|
this.skip(reason);
|
|
}
|
|
}
|
|
// Just throw and catch an exception.
|
|
window.onerror = function () { return true; }
|
|
throw new Error("__SKIP_REST__");
|
|
};
|
|
|
|
Test.Builder.prototype.useNumbers = function (useNums) {
|
|
if (useNums != null) this.UseNums = useNums;
|
|
return this.UseNums;
|
|
};
|
|
|
|
Test.Builder.prototype.noHeader = function (noHeader) {
|
|
if (noHeader != null) this.NoHeader = !!noHeader;
|
|
return this.NoHeader;
|
|
};
|
|
|
|
Test.Builder.prototype.noEnding = function (noEnding) {
|
|
if (noEnding != null) this.NoEnding = !!noEnding;
|
|
return this.NoEnding;
|
|
};
|
|
|
|
Test.Builder.prototype.diag = function () {
|
|
if (!arguments.length) return;
|
|
|
|
var msg = '# ';
|
|
// Join each agument and escape each line with a #.
|
|
for (i = 0; i < arguments.length; i++) {
|
|
// Replace any newlines.
|
|
msg += arguments[i].toString().replace(Test.Builder.lineEndingRx,
|
|
Test.Builder.LF + "# ");
|
|
}
|
|
|
|
// Append a new line to the end of the message if there isn't one.
|
|
if (!(new RegExp(Test.Builder.LF + '$').test(msg)))
|
|
msg += Test.Builder.LF;
|
|
// Append the diag message to the most recent result.
|
|
return this._printDiag(msg);
|
|
};
|
|
|
|
Test.Builder.prototype._printDiag = function () {
|
|
var fn = this.todo() ? this.todoOutput() : this.failureOutput();
|
|
fn.apply(this, arguments);
|
|
return false;
|
|
};
|
|
|
|
Test.Builder.prototype.output = function (fn) {
|
|
if (fn != null) {
|
|
var buffer = this.Buffer;
|
|
this.Output = function (msg) { buffer.push(msg); fn(msg) };
|
|
}
|
|
return this.Output;
|
|
};
|
|
|
|
Test.Builder.prototype.failureOutput = function (fn) {
|
|
if (fn != null) {
|
|
var buffer = this.Buffer;
|
|
this.FailureOutput = function (msg) { buffer.push(msg); fn(msg) };
|
|
}
|
|
return this.FailureOutput;
|
|
};
|
|
|
|
Test.Builder.prototype.todoOutput = function (fn) {
|
|
if (fn != null) {
|
|
var buffer = this.Buffer;
|
|
this.TodoOutput = function (msg) { buffer.push(msg); fn(msg) };
|
|
}
|
|
return this.TodoOutput;
|
|
};
|
|
|
|
Test.Builder.prototype.endOutput = function (fn) {
|
|
if (fn != null) {
|
|
var buffer = this.Buffer;
|
|
this.EndOutput = function (msg) { buffer.push(msg); fn(msg) };
|
|
}
|
|
return this.EndOutput;
|
|
};
|
|
|
|
Test.Builder.prototype.warnOutput = function (fn) {
|
|
if (fn != null) {
|
|
var buffer = this.Buffer;
|
|
this.WarnOutput = function (msg) { buffer.push(msg); fn(msg) };
|
|
}
|
|
return this.WarnOutput;
|
|
};
|
|
|
|
Test.Builder.prototype._setupOutput = function () {
|
|
if (Test.PLATFORM == 'browser') {
|
|
var writer = function (msg) {
|
|
// I'm sure that there must be a more efficient way to do this,
|
|
// but if I store the node in a variable outside of this function
|
|
// and refer to it via the closure, then things don't work right
|
|
// --the order of output can become all screwed up (see
|
|
// buffer.html). I have no idea why this is.
|
|
var node = document.getElementById("test");
|
|
if (node) {
|
|
// This approach is neater, but causes buffering problems when
|
|
// mixed with document.write. See tests/buffer.html.
|
|
//node.appendChild(document.createTextNode(msg));
|
|
//return;
|
|
for (var i = 0; i < node.childNodes.length; i++) {
|
|
if (node.childNodes[i].nodeType == 3 /* Text Node */) {
|
|
// Append to the node and scroll down.
|
|
node.childNodes[i].appendData(msg);
|
|
window.scrollTo(0, document.body.offsetHeight
|
|
|| document.body.scrollHeight);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If there was no text node, add one.
|
|
node.appendChild(document.createTextNode(msg));
|
|
window.scrollTo(0, document.body.offsetHeight
|
|
|| document.body.scrollHeight);
|
|
return;
|
|
}
|
|
|
|
// Default to the normal write and scroll down...
|
|
document.write(msg);
|
|
window.scrollTo(0, document.body.offsetHeight
|
|
|| document.body.scrollHeight);
|
|
};
|
|
|
|
this.output(writer);
|
|
this.failureOutput(writer);
|
|
this.todoOutput(writer);
|
|
this.endOutput(writer);
|
|
|
|
if (window) {
|
|
if (window.alert.apply) this.warnOutput(window.alert, window);
|
|
else this.warnOutput(function (msg) { window.alert(msg) });
|
|
}
|
|
|
|
} else if (Test.PLATFORM == 'director') {
|
|
// Macromedia-Adobe:Director MX 2004 Support
|
|
// XXX Is _player a definitive enough object?
|
|
// There may be an even more explicitly Director object.
|
|
this.output(trace);
|
|
this.failureOutput(trace);
|
|
this.todoOutput(trace);
|
|
this.warnOutput(trace);
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
Test.Builder.prototype.currentTest = function (num) {
|
|
if (num == null) return this.CurrTest;
|
|
|
|
if (!this.HavePlan)
|
|
Test.Builder.die("Can't change the current test number without a plan!");
|
|
this.CurrTest = num;
|
|
if (num > this.TestResults.length ) {
|
|
var reason = 'incrementing test number';
|
|
for (i = this.TestResults.length; i < num; i++) {
|
|
this.TestResults[i] = {
|
|
ok: true,
|
|
actual_ok: null,
|
|
reason: reason,
|
|
type: 'unknown',
|
|
name: null,
|
|
output: 'ok - ' + reason + Test.Builder.LF
|
|
};
|
|
}
|
|
} else if (num < this.TestResults.length) {
|
|
// IE requires the second argument to truncate the array.
|
|
this.TestResults.splice(num, this.TestResults.length);
|
|
}
|
|
return this.CurrTest;
|
|
};
|
|
|
|
Test.Builder.prototype.summary = function () {
|
|
var results = new Array(this.TestResults.length);
|
|
for (var i = 0; i < this.TestResults.length; i++) {
|
|
results[i] = this.TestResults[i]['ok'];
|
|
}
|
|
return results
|
|
};
|
|
|
|
Test.Builder.prototype.details = function () {
|
|
return this.TestResults;
|
|
};
|
|
|
|
Test.Builder.prototype.todo = function (why, howMany) {
|
|
if (howMany) this.ToDo = [why, howMany];
|
|
return this.ToDo[1];
|
|
};
|
|
|
|
Test.Builder.prototype._todo = function () {
|
|
if (this.ToDo[1]) {
|
|
if (this.ToDo[1]--) return this.ToDo[0];
|
|
this.ToDo = [];
|
|
}
|
|
return false;
|
|
};
|
|
|
|
Test.Builder.prototype._sanity_check = function () {
|
|
Test.Builder._whoa(
|
|
this.CurrTest < 0,
|
|
'Says here you ran a negative number of tests!'
|
|
);
|
|
|
|
Test.Builder._whoa(
|
|
!this.HavePlan && this.CurrTest,
|
|
'Somehow your tests ran without a plan!'
|
|
);
|
|
|
|
Test.Builder._whoa(
|
|
this.CurrTest != this.TestResults.length,
|
|
'Somehow you got a different number of results than tests ran!'
|
|
);
|
|
};
|
|
|
|
Test.Builder.prototype._notifyHarness = function () {
|
|
// Special treatment for the browser harness.
|
|
if (typeof window != 'undefined' && window.parent
|
|
&& window.parent.Test && window.parent.Test.Harness) {
|
|
window.parent.Test.Harness.Done++;
|
|
}
|
|
};
|
|
|
|
Test.Builder.prototype._ending = function () {
|
|
if (this.Ended) return;
|
|
this.Ended = true;
|
|
if (this.noEnding()) {
|
|
this._notifyHarness();
|
|
return;
|
|
}
|
|
this._sanity_check();
|
|
var out = this.endOutput();
|
|
|
|
// Figure out if we passed or failed and print helpful messages.
|
|
if( this.TestResults.length ) {
|
|
// The plan? We have no plan.
|
|
if (this.NoPlan) {
|
|
if (!this.noHeader())
|
|
this._print("1.." + this.CurrTest + Test.Builder.LF);
|
|
this.ExpectedTests = this.CurrTest;
|
|
}
|
|
|
|
var numFailed = 0;
|
|
for (var i = 0; i < this.TestResults.length; i++) {
|
|
if (!this.TestResults[i]) numFailed++;
|
|
}
|
|
numFailed += Math.abs(
|
|
this.ExpectedTests - this.TestResults.length
|
|
);
|
|
|
|
if (this.CurrTest < this.ExpectedTests) {
|
|
var s = this.ExpectedTests == 1 ? '' : 's';
|
|
out(
|
|
"# Looks like you planned " + this.ExpectedTests + " test"
|
|
+ s + " but only ran " + this.CurrTest + "." + Test.Builder.LF
|
|
);
|
|
} else if (this.CurrTest > this.ExpectedTests) {
|
|
var numExtra = this.CurrTest - this.ExpectedTests;
|
|
var s = this.ExpectedTests == 1 ? '' : 's';
|
|
out(
|
|
"# Looks like you planned " + this.ExpectedTests + " test"
|
|
+ s + " but ran " + numExtra + " extra." + Test.Builder.LF
|
|
);
|
|
} else if (numFailed) {
|
|
var s = numFailed == 1 ? '' : 's';
|
|
out(
|
|
"# Looks like you failed " + numFailed + "test" + s + " of "
|
|
+ this.ExpectedTests + "." + Test.Builder.LF
|
|
);
|
|
}
|
|
|
|
if (this.TestDied) {
|
|
out(
|
|
"# Looks like your test died just after "
|
|
+ this.CurrTest + "." + Test.Builder.LF
|
|
);
|
|
}
|
|
|
|
} else if (!this.SkipAll) {
|
|
// skipAll requires no status output.
|
|
if (this.TestDied) {
|
|
out(
|
|
"# Looks like your test died before it could output anything."
|
|
+ Test.Builder.LF
|
|
);
|
|
} else {
|
|
out("# No tests run!" + Test.Builder.LF);
|
|
}
|
|
}
|
|
this._notifyHarness();
|
|
};
|
|
|
|
Test.Builder.prototype.isUndef = function (got, op, expect, desc) {
|
|
// Undefined only matches undefined, so we don't need to cast anything.
|
|
var test = eval("got " + (Test.Builder.StringOps[op] || op) + " expect");
|
|
this.ok(test, desc);
|
|
if (!test) this._isDiag(got, op, expect);
|
|
return test;
|
|
};
|
|
|
|
if (window) {
|
|
// Set up an onload function to end all tests.
|
|
window.onload = function () {
|
|
for (var i = 0; i < Test.Builder.Instances.length; i++) {
|
|
// The main process is always async ID 0.
|
|
Test.Builder.Instances[i].endAsync(0);
|
|
}
|
|
};
|
|
|
|
// Set up an exception handler. This is so that we can capture deaths but
|
|
// still output information for TestHarness to pick up.
|
|
window.onerror = function (msg, url, line) {
|
|
// Output the exception.
|
|
Test.Builder.Test.TestDied = true;
|
|
Test.Builder.Test.diag("Error in " + url + " at line " + line + ": " + msg);
|
|
return true;
|
|
};
|
|
};
|
|
|
|
Test.Builder.prototype.beginAsync = function (timeout) {
|
|
var id = ++this.asyncID;
|
|
if (timeout && window && window.setTimeout) {
|
|
// Are there other ways of setting timeout in non-browser settings?
|
|
var aTest = this;
|
|
this.asyncs[id] = window.setTimeout(
|
|
function () { aTest.endAsync(id) }, timeout
|
|
);
|
|
} else {
|
|
// Make sure it's defined.
|
|
this.asyncs[id] = 0;
|
|
}
|
|
return id;
|
|
};
|
|
|
|
Test.Builder.prototype.endAsync = function (id) {
|
|
if (this.asyncs[id] == undefined) return;
|
|
if (this.asyncs[id]) {
|
|
// Remove the timeout
|
|
window.clearTimeout(this.asyncs[id]);
|
|
}
|
|
if (--this.asyncID < 0) this._ending();
|
|
};
|
|
|
|
Test.Builder.exporter = function (pkg, root) {
|
|
if (typeof root == 'undefined') {
|
|
if (Test.PLATFORM == 'browser') root = window;
|
|
else if (Test.PLATFORM == 'director') root = _global;
|
|
else throw new Error("Platform unknown");
|
|
}
|
|
for (var i = 0; i < pkg.EXPORT.length; i++) {
|
|
if (typeof root[pkg.EXPORT[i]] == 'undefined')
|
|
root[pkg.EXPORT[i]] = pkg[pkg.EXPORT[i]];
|
|
}
|
|
}; |