Moved the tests into jQuery proper.
[jquery.git] / test / lib / Test / Builder.js
diff --git a/test/lib/Test/Builder.js b/test/lib/Test/Builder.js
new file mode 100644 (file)
index 0000000..1435082
--- /dev/null
@@ -0,0 +1,797 @@
+// # $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]];
+    }
+};
\ No newline at end of file