Moved the tests into jQuery proper.
authorJohn Resig <jeresig@gmail.com>
Sun, 13 Aug 2006 15:24:52 +0000 (15:24 +0000)
committerJohn Resig <jeresig@gmail.com>
Sun, 13 Aug 2006 15:24:52 +0000 (15:24 +0000)
17 files changed:
test/index.html [new file with mode: 0644]
test/lib/Test/Builder.js [new file with mode: 0644]
test/lib/Test/Harness.js [new file with mode: 0644]
test/lib/Test/Harness/Browser.js [new file with mode: 0644]
test/lib/Test/Harness/Director.js [new file with mode: 0755]
test/lib/Test/More.js [new file with mode: 0644]
test/lib/Test/Simple.js [new file with mode: 0644]
test/lib/Test/tmp.js [new file with mode: 0644]
test/lib/Test/tmp2.js [new file with mode: 0644]
test/lib/test.js [new file with mode: 0644]
test/tests/basic.html [new file with mode: 0644]
test/tests/css1.html [new file with mode: 0644]
test/tests/css2.html [new file with mode: 0644]
test/tests/css3.html [new file with mode: 0644]
test/tests/custom.html [new file with mode: 0644]
test/tests/prereq.html [new file with mode: 0644]
test/tests/xpath.html [new file with mode: 0644]

diff --git a/test/index.html b/test/index.html
new file mode 100644 (file)
index 0000000..ac90376
--- /dev/null
@@ -0,0 +1,21 @@
+<html>
+<head>
+       <script type="text/javascript" src="lib/Test/Harness.js"></script>
+       <script type="text/javascript" src="lib/Test/Harness/Browser.js"></script>
+</head>
+<body>
+       <h1>jQuery - Test Suite</h1>
+
+       <script type="text/javascript">
+               new Test.Harness.Browser().runTests(
+                       "tests/prereq.html",
+                       "tests/css1.html",
+                       "tests/css2.html",
+                       "tests/css3.html",
+                       "tests/xpath.html",
+                       "tests/custom.html",
+                       "tests/basic.html"
+               );
+       </script>
+</body>
+</html>
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
diff --git a/test/lib/Test/Harness.js b/test/lib/Test/Harness.js
new file mode 100644 (file)
index 0000000..e43dc5c
--- /dev/null
@@ -0,0 +1,267 @@
+// # $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 _player != 'undefined'){
+    //Director
+    if (typeof _global.Test != "object") _global.Test = {PLATFORM: 'director'};
+    else _global.Test.PLATFORM = 'director';
+} else {
+    throw new Error("Test.Harness does not support your platform");
+}
+
+Test.Harness = function () {};
+Test.Harness.VERSION = '0.11';
+Test.Harness.Done = 0;
+
+// Stoopid IE.
+Test.Harness.LF = typeof document != "undefined"
+                  && typeof document.all != "undefined"
+  ? "\r"
+  : "\n";
+
+Test.Harness.prototype.isDone = Test.Harness.isDone;
+
+/*
+
+    bonus           Number of individual todo tests unexpectedly passed
+    ran             Number of individual tests ran
+    ok              Number of individual tests passed
+    subSkipped      Number of individual tests skipped
+    todo            Number of individual todo tests
+
+    files           Number of test files ran
+    good            Number of test files passed
+    bad             Number of test files failed
+    tests           Number of test files originally given
+    skipped         Number of test files skipped
+
+*/
+
+Test.Harness.prototype.bonus      = 0;
+Test.Harness.prototype.ran        = 0;
+Test.Harness.prototype.ok         = 0;
+Test.Harness.prototype.subSkipped = 0;
+Test.Harness.prototype.todo       = 0;
+Test.Harness.prototype.files      = 0;
+Test.Harness.prototype.good       = 0;
+Test.Harness.prototype.bad        = 0;
+Test.Harness.prototype.tests      = 0;
+Test.Harness.prototype.skipped    = 0;
+Test.Harness.prototype.failures   = [];
+
+Test.Harness.runTests = function () {
+    // XXX Can't handle inheritance, right? Or can we?
+    var harness = new Test.Harness();
+    harness.runTests.apply(harness, arguments);
+};
+
+Test.Harness.prototype.outFileNames = function (files) {
+    var len = 0;
+    for (var i = 0; i < files.length; i++) {
+        if (files[i].length > len) len = files[i].length;
+    }
+    len += 3;
+    var ret = [];
+    for (var i = 0; i < files.length; i++) {
+        var outName = files[i];
+        var add = len - files[i].length;
+        // Where is Perl's x operator when I need it??
+        for (var j = 0; j < add; j++) {
+            outName += '.';
+        }
+        ret.push(outName);
+    }
+    return ret;
+};
+
+Test.Harness.prototype.outputResults = function (test, file, fn, attrs) {
+    this.tests++;
+    this.ran += test.TestResults.length;
+    var fails = [];
+    var track = {
+      fn:       file,
+      total:    test.expectedTests,
+      ok:       0,
+      failList: []
+    };
+
+    if (test.TestResults.length) {
+        this.files++;
+        var pass = true;
+        for (var i = 0; i < test.TestResults.length; i++) {
+            var ok = "ok";
+            if (test.TestResults[i].ok) {
+                this.ok++;
+                track.ok++
+                if (test.TestResults[i].type == 'todo') {
+                    // Handle unexpected pass.
+                    if (test.TestResults[i].actualOK) this.bonus++;
+                    this.todo ++;
+                } else if (test.TestResults[i].type == 'skip') this.subSkipped++;
+            } else {
+                if (test.TestResults[i].type == 'todo') {
+                    // Expected failure.
+                    this.todo++;
+                } else {
+                    pass = false;
+                    track.failList.push(i + 1);
+                }
+                ok = "not ok"; // XXX Need to handle TODO and TODO Skipped.
+            }
+            
+            if (!pass || attrs.verbose) fn(test.TestResults[i].output);
+        }
+        
+        if (pass) {
+            this.good++;
+            fn("ok" + Test.Harness.LF);
+        } else {
+            this.bad++;
+            var err = "NOK # Failed ";
+            if (track.failList.length == 1) {
+                err += "test " + track.failList[0];
+            } else {
+                err += "tests " + this._failList(track.failList);
+            }
+            fn(err + " in " + file + Test.Harness.LF);
+        }
+    } else if (test.SkipAll){
+        // All tests skipped.
+        this.skipped++;
+        this.good++;
+        fn("1..0 # Skip 1" + Test.Harness.LF);
+    } else {
+        // Wha happened? Tests ran, but no results!
+        this.files++;
+        this.bad++;
+        fn("FAILED before any test output arrived" + Test.Harness.LF);
+    }
+    if (track.failList.length) this.failures.push(track);
+};
+
+Test.Harness.prototype._allOK = function () {
+    return this.bad == 0 && (this.ran || this.skipped) ? true : false;
+};
+
+Test.Harness.prototype.outputSummary = function (fn, time) {
+    var bonusmsg = this._bonusmsg();
+    var pct;
+    if (this._allOK()) {
+        fn("All tests successful" + bonusmsg + '.' + Test.Harness.LF);
+    } else if (!this.tests) {
+        fn("FAILED—no tests were run for some reason." + Test.Harness.LF);
+    } else if (!this.ran) {
+        var blurb = this.tests == 1 ? "file" : "files";
+        fn("FAILED—" + this.tests + " test " + blurb + " could be run, "
+           + "alas—no output ever seen." + Test.Harness.LF);
+    } else {
+        pct = this.good / this.tests * 100;
+        var pctOK = 100 * this.ok / this.ran;
+        var subpct = (this.ran - this.ok) + "/" + this.ran
+          + " subtests failed, " + pctOK.toPrecision(4) + "% okay.";
+
+        if (this.bad) {
+            bonusmsg = bonusmsg.replace(/^,?\s*/, '');
+            if (bonusmsg) fn(bonusmsg + '.' + Test.Harness.LF);
+            fn("Failed " + this.bad + "/" + this.tests + " test scripts, "
+               + pct.toPrecision(4) + "% okay. " + subpct + Test.Harness.LF);
+        }
+        this.formatFailures(fn);
+    }
+
+    fn("Files=" + this.tests + ", Tests=" + this.ran + ", " + (time / 1000)
+       + " seconds" + Test.Harness.LF);
+};
+
+Test.Harness.prototype.formatFailures = function () {
+    var table = '';
+    var failedStr = "Failed Test";
+    var middleStr = " Total Fail  Failed  ";
+    var listStr = "List of Failed";
+    var cols = 80;
+
+    // Figure out our longest name string for formatting purposes.
+    var maxNamelen = failedStr.length;
+    for (var i = 0; i < this.failures.length; i++) {
+        var len = this.failures[i].length;
+        if (len > maxNamelen) maxNamelen = len;
+    }
+
+    var listLen = cols - middleStr.length - maxNamelen.length;
+    if (listLen < listStr.length) {
+        listLen = listStr.length;
+        maxNamelen = cols - middleStr.length - listLen;
+        if (maxNamelen < failedStr.length) {
+            maxNamelen = failedStr.length;
+            cols = maxNamelen + middleStr.length + listLen;
+        }
+    }
+
+    var out = failedStr;
+    if (out.length < maxNamelen) {
+        for (var j = out.length; j < maxNameLength; j++) {
+            out += ' ';
+        }
+    }
+    out += '  ' + middleStr;
+    // XXX Need to finish implementing the text-only version of the failures
+    // table.
+};
+
+Test.Harness.prototype._bonusmsg = function () {
+    var bonusmsg = '';
+    if (this.bonus) {
+        bonusmsg = (" (" + this.bonus + " subtest" + (this.bonus > 1 ? 's' : '')
+          + " UNEXPECTEDLY SUCCEEDED)");
+    }
+
+    if (this.skipped) {
+        bonusmsg += ", " + this.skipped + " test"
+          + (this.skipped != 1 ? 's' : '');
+        if (this.subSkipped) {
+            bonusmsg += " and " + this.subSkipped + " subtest"
+              + (this.subSkipped != 1 ? 's' : '');
+        }
+        bonusmsg += ' skipped';
+    } else if (this.subSkipped) {
+        bonusmsg += ", " + this.subSkipped + " subtest"
+          + (this.subSkipped != 1 ? 's' : '') + " skipped";
+    }
+
+    return bonusmsg;
+}
+
+Test.Harness.prototype._failList = function (fails) {
+    var last = -1;
+    var dash = '';
+    var list = [];
+    for (var i = 0; i < fails.length; i++) {
+        if (dash) {
+            // We're in a series of numbers.
+            if (fails[i] - 1 == last) {
+                // We're still in it.
+                last = fails[i];
+            } else {
+                // End of the line.
+                list[list.length-1] += dash + last;
+                last = -1;
+                list.push(fails[i]);
+                dash = '';
+            }
+        } else if (fails[i] - 1 == last) {
+            // We're in a new series.
+            last = fails[i];
+            dash = '-';
+        } else {
+            // Not in a sequence.
+            list.push(fails[i]);
+            last = fails[i];
+        }
+    }
+    if (dash) list[list.length-1] += dash + last;
+    return list.join(' ');
+}
diff --git a/test/lib/Test/Harness/Browser.js b/test/lib/Test/Harness/Browser.js
new file mode 100644 (file)
index 0000000..4fa4cd2
--- /dev/null
@@ -0,0 +1,176 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+
+if (typeof JSAN != 'undefined') new JSAN().use('Test.Harness');
+
+Test.Harness.Browser = function () {};
+Test.Harness.Browser.VERSION = '0.11';
+
+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 (a) {
+    // Setup the iFrame to run the tests.
+    var node = document.getElementById('buffer');
+    if (node) return node.contentWindow;
+    node = document.createElement("iframe");
+    node.setAttribute("id", "buffer");
+    node.setAttribute("name", "buffer");
+               node.style.visibility = "hidden";
+               node.style.height = 0; 
+               node.style.width = 0;
+    document.body.appendChild(node);
+               
+               var self = this;
+               setTimeout( function(){
+                       self.buffer = node.contentWindow;
+                       self._runTests.apply(self,a);
+               }, 200 );
+};
+
+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);
+    return function (msg) {
+        node.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 () {
+       this._setupFrame(arguments);
+}
+
+Test.Harness.Browser.prototype._runTests = function () {
+    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.buffer;
+    var harness = this;
+    var ti = 0;
+    var start;
+    var node = document.getElementById('output');
+    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(outfiles[ti] + (harness.args.verbose ? Test.Harness.LF : ''));
+            buffer.location.href = files[ti] + "?" + start.getTime();
+            runNext();
+        } else {
+            harness.outputSummary(
+                summaryOutput,
+                new Date() - start
+             );
+            finish();
+        }
+    };
+
+    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 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.
+            if (Test.Harness.Done > 0
+                && Test.Harness.Done >= buffer.Test.Builder.Instances.length)
+            {
+                Test.Harness.Done = 0;
+                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(outfiles[ti] + (this.args.verbose ? Test.Harness.LF : ''));
+    start = new Date();
+    buffer.location.href = files[ti] + "?" + start.getTime(); // replace() doesn't seem to work.
+};
+
+// From "JavaScript: The Difinitive Guide 4ed", p 214.
+Test.Harness.Browser.prototype.args = {};
+var pairs = location.search.substring(1).split(",");
+for (var i = 0; i < pairs.length; i++) {
+    var pos = pairs[i].indexOf('=');
+    if (pos == -1) continue;
+    var key = pairs[i].substring(0, pos);
+    var val = pairs[i].substring(pos + 1); 
+    if (Test.Harness.Browser.prototype.args[key]) {
+        if (typeof Test.Harness.Browser.prototype.args[key] == 'string') {
+            Test.Harness.Browser.prototype.args[key] =
+                [Test.Harness.Browser.prototype.args[key]];
+        }
+        Test.Harness.Browser.prototype.args[key].push(unescape(val));
+    } else {
+        Test.Harness.Browser.prototype.args[key] = unescape(val);
+    }
+}
+delete pairs;
+
+Test.Harness.Browser.prototype.formatFailures = function (fn) {
+    // XXX append new element for table and then populate it.
+    var failedStr = "Failed Test";
+    var middleStr = " Total Fail  Failed  ";
+    var listStr = "List of Failed";
+    var table = '<table style=""><tr><th>Failed Test</th><th>Total</th>'
+      + '<th>Fail</th><th>Failed</th></tr>';
+    for (var i = 0; i < this.failures.length; i++) {
+        var track = this.failures[i];
+        table += '<tr><td>' + track.fn + '</td>'
+          + '<td>' + track.total + '</td>'
+          + '<td>' + track.total - track.ok + '</td>'
+          + '<td>' + this._failList(track.failList) + '</td></tr>'
+    };
+    table += '</table>' + Test.Harness.LF;
+    var node = document.getElementById('summary');
+    node.innerHTML += table;
+    window.scrollTo(0, document.body.offsetHeight || document.body.scrollHeight);
+};
diff --git a/test/lib/Test/Harness/Director.js b/test/lib/Test/Harness/Director.js
new file mode 100755 (executable)
index 0000000..63d676b
--- /dev/null
@@ -0,0 +1,61 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+
+Test.Harness.Director = function () {};
+Test.Harness.Director.VERSION = '0.11';
+
+Test.Harness.Director.runTests = function () {
+    var harness = new Test.Harness.Director();
+    harness.runTests.apply(harness, arguments);
+};
+
+Test.Harness.Director.prototype = new Test.Harness();
+Test.Harness.Director.prototype.verbose = true;
+Test.Harness.Director.prototype.args = {};
+
+Test.Harness.Director.prototype.runTests = function (x_aFunctionNames) {
+    // Allow for an array or a simple list in arguments.
+    // XXX args.file isn't quite right since it's more function names, but
+    // that is still to be ironed out.
+    var functionNames = this.args.file
+      ? typeof this.args.file == 'string' ? [this.args.file] : this.args.file
+      : arguments;
+    if (!x_aFunctionNames.length) return;
+    var outfunctions = this.outFileNames(x_aFunctionNames);
+    var harness = this;
+    var start = new Date();
+    var newLineRx = /(?:\r?\n|\r)+$/;
+    var output = function (msg) { trace(msg.replace(newLineRx, '')) };
+
+    for (var x = 0; x < x_aFunctionNames.length; x++){
+        output(outfunctions[x]);
+        eval(x_aFunctionNames[x] + "()");
+        harness.outputResults(
+            Test.Builder.Test,
+            x_aFunctionNames[x],
+            output,
+            harness.args
+        );
+    }
+    harness.outputSummary(
+        output,
+        new Date() - start
+    );
+};
+
+Test.Harness.Director.prototype.formatFailures = function (fn) {
+    // XXX Delete once the all-text version is implemented in Test.Harness.
+    var failedStr = "Failed Test";
+    var middleStr = " Total Fail  Failed  ";
+    var listStr = "List of Failed";
+    var table = '<table style=""><tr><th>Failed Test</th><th>Total</th>'
+      + '<th>Fail</th><th>Failed</th></tr>';
+    for (var i = 0; i < this.failures.length; i++) {
+        var track = this.failures[i];
+        table += '<tr><td>' + track.fn + '</td>'
+          + '<td>' + track.total + '</td>'
+          + '<td>' + track.total - track.ok + '</td>'
+          + '<td>' + this._failList(track.failList) + '</td></tr>'
+    };
+    table += '</table>';
+    output(table);
+};
\ No newline at end of file
diff --git a/test/lib/Test/More.js b/test/lib/Test/More.js
new file mode 100644 (file)
index 0000000..da94800
--- /dev/null
@@ -0,0 +1,429 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+// Create a namespace for ourselves.
+
+// Set up package.
+if (typeof JSAN != 'undefined') new JSAN().use('Test.Builder');
+else {
+    if (typeof Test == 'undefined' || typeof Test.Builder == 'undefined')
+        throw new Error(
+            "You must load either JSAN or Test.Builder "
+            + "before loading Test.More"
+        );
+}
+
+Test.More = {};
+Test.More.EXPORT = [
+    'plan',
+    'ok', 'is', 'isnt',
+    'like', 'unlike',
+    'cmpOK', 'canOK', 'isaOK',
+    'pass', 'fail', 'diag', 'loadOK',
+    'skip', 'todo', 'todoSkip', 'skipRest',
+    'isDeeply', 'isSet', 'isa'
+];
+Test.More.EXPORT_TAGS = { ':all': Test.More.EXPORT };
+Test.More.VERSION     = '0.11';
+
+Test.More.ShowDiag = true;
+Test.Builder.DNE = { dne: 'Does not exist' };
+Test.More.Test = new Test.Builder();
+Test.More.builder = function () { return Test.More.Test; };
+
+Test.More.plan = function (cmds) {
+    if (cmds.noDiag) {
+        Test.More.ShowDiag = false;
+        delete cmds.noDiag;
+    }
+    return Test.More.Test.plan.apply(Test.More.Test, [cmds]);
+};
+
+Test.More.ok = function (test, desc) {
+    return Test.More.Test.ok(test, desc);
+};
+
+Test.More.is = function (got, expect, desc) {
+    return Test.More.Test.isEq(got, expect, desc);
+};
+
+Test.More.isnt = function (got, expect, desc) {
+    return Test.More.Test.isntEq(got, expect, desc);
+};
+
+Test.More.like = function (val, regex, desc) {
+    return Test.More.Test.like(val, regex, desc);
+};
+
+Test.More.unlike = function (val, regex, desc) {
+    return Test.More.Test.unlike(val, regex, desc);
+};
+
+Test.More.cmpOK = function (got, op, expect, desc) {
+    return Test.More.Test.cmpOK(got, op, expect, desc);
+};
+
+Test.More.canOK = function (proto) {
+    var ok;
+    // Make sure they passed some method names for us to check.
+    if (!arguments.length > 1) {
+        ok = Test.More.Test.ok(false, clas + '.can(...)');
+        Test.More.Test.diag('    canOK() called with no methods');
+        return ok;
+    }
+
+    // Get the class name and the prototype.
+    var clas;
+    if (typeof proto == 'string') {
+        // We just have a class name.
+        clas = proto;
+        proto = eval(clas + '.prototype');
+    } else {
+        // We have an object or something that can be converted to an object.
+        clas = Test.Builder.typeOf(proto);
+        proto = proto.constructor.prototype;
+    }
+
+    var nok = [];
+    for (var i = 1; i < arguments.length; i++) {
+        var method = arguments[i];
+        if (typeof proto[method] != 'function') nok.push(method);
+    }
+
+    // There'es no can() method in JavaScript, but what the hell!
+    var desc = clas + ".can('" + (arguments.length == 2 ? arguments[1] : '...') + "')";
+    ok = Test.More.Test.ok(!nok.length, desc);
+    for (var i = 0; i < nok.length; i++) {
+        Test.More.Test.diag('    ' + clas + ".can('" + nok[i] + "') failed");
+    }
+    return ok;
+};
+
+Test.More.isaOK = function (object, clas, objName) {
+    var mesg;
+    if (objName == null) objName = 'The object';
+    var name = objName + ' isa ' + clas;
+    if (object == null) {
+        mesg = objName + " isn't defined";
+    } else if (!Test.More._isRef(object)) {
+        mesg = objName + " isn't a reference";
+    } else {
+        var ctor = eval(clas);
+        if (Object.isPrototypeOf) {
+            // With JavaScript 1.5, we can determine inheritance.
+            if (!ctor.prototype.isPrototypeOf(object)) {
+                mesg = objName + " isn't a '" + clas + "' it's a '"
+                  + Test.Builder.typeOf(object) + "'";
+            }
+        } else {
+            // We can just determine what constructor was used. This will
+            // not work for inherited constructors.
+            if (object.constructor != ctor)
+                mesg = objName + " isn't a '" + clas + "' it's a '"
+                  + Test.Builder.typeOf(object) + '"';
+        }
+    }
+    
+    var ok;
+    if (mesg) {
+        ok = Test.More.Test.ok(false, name);
+        Test.More.Test.diag('    ' + mesg);
+    } else {
+        ok = Test.More.Test.ok(true, name);
+    }
+
+    return ok;
+};
+
+Test.More.pass = function (name) {
+    return Test.More.Test.ok(true, name);
+};
+
+Test.More.fail = function (name) {
+    return Test.More.Test.ok(false, name);
+};
+
+Test.More.diag = function () {
+    if (!Test.More.ShowDiag) return;
+    return Test.More.Test.diag.apply(Test.More.Test, arguments);
+};
+
+// Use this instead of use_ok and require_ok.
+Test.More.loadOK = function () {
+    // XXX What do I do here? Eval?
+    // XXX Just always fail for now, to keep people from using it just yet.
+    return false;
+};
+
+Test.More.skip = function (why, howMany) {
+    if (howMany == null) {
+        if (!Test.Builder.NoPlan)
+            Test.More.Test.warn("skip() needs to know howMany tests are in the block");
+        howMany = 1;
+    }
+    for (i = 0; i < howMany; i++) {
+        Test.More.Test.skip(why);
+    }
+};
+
+Test.More.todo = function (why, howMany) {
+    if (howMany == null) {
+        if (!Test.Builder.NoPlan)
+            Test.More.Test.warn("todo() needs to know howMany tests are in the block");
+        howMany = 1;
+    }
+    return Test.More.Test.todo(why, howMany);
+};
+
+Test.More.todoSkip = function (why, howMany) {
+    if (howMany == null) {
+        if (!Test.Builder.NoPlan)
+            Test.More.Test.warn("todoSkip() needs to know howMany tests are in the block");
+        howMany = 1;
+    }
+
+    for (i = 0; i < howMany; i++) {
+        Test.More.Test.todoSkip(why);
+    }
+};
+
+Test.More.skipRest = function (why) {
+    Test.More.Test.skipRest(why);
+};
+
+Test.More.isDeeply = function (it, as, name) {
+    if (arguments.length != 2 && arguments.length != 3) {
+        Test.More.Test.warn(
+            'isDeeply() takes two or three args, you gave '
+            + arguments.length + "."
+        );
+    }
+
+    var ok;
+    // ^ is the XOR operator.
+    if (Test.More._isRef(it) ^ Test.More._isRef(as)) {
+        // One's a reference, one isn't.
+        ok = false;
+    } else if (!Test.More._isRef(it) && !Test.More._isRef(as)) {
+        // Neither is an object.
+        ok = Test.More.Test.isEq(it, as, name);
+    } else {
+        // We have two objects. Do a deep comparison.
+        var stack = [], seen = [];
+        if ( Test.More._deepCheck(it, as, stack, seen)) {
+            ok = Test.More.Test.ok(true, name);
+        } else {
+            ok = Test.More.Test.ok(false, name);
+            Test.More.Test.diag(Test.More._formatStack(stack));
+        }
+    }
+    return ok;
+};
+
+Test.More._deepCheck = function (e1, e2, stack, seen) {
+    var ok = false;
+    // Either they're both references or both not.
+    var sameRef = !(!Test.More._isRef(e1) ^ !Test.More._isRef(e2));
+    if (e1 == null && e2 == null) {
+        ok = true;
+    } else if (e1 != null ^ e2 != null) {
+        ok = false;
+    } else if (e1 == Test.More.DNE ^ e2 == Test.More.DNE) {
+        ok = false;
+    } else if (sameRef && e1 == e2) {
+        // Handles primitives and any variables that reference the same
+        // object, including functions.
+        ok = true;
+    } else if (isa(e1, 'Array') && isa(e2, 'Array')) {
+        ok = Test.More._eqArray(e1, e2, stack, seen);
+    } else if (typeof e1 == "object" && typeof e2 == "object") {
+        ok = Test.More._eqAssoc(e1, e2, stack, seen);
+    } else {
+        // If we get here, they're not the same (function references must
+        // always simply rererence the same function).
+        stack.push({ vals: [e1, e2] });
+        ok = false;
+    }
+    return ok;
+};
+
+Test.More._isRef = function (object) {
+    var type = typeof object;
+    return type == 'object' || type == 'function';
+};
+
+Test.More._formatStack = function (stack) {
+    var variable = '$Foo';
+    for (var i = 0; i < stack.length; i++) {
+        var entry = stack[i];
+        var type = entry['type'];
+        var idx = entry['idx'];
+        if (idx != null) {
+            if (/^\d+$/.test(idx)) {
+                // Numeric array index.
+                variable += '[' + idx + ']';
+            } else {
+                // Associative array index.
+                idx = idx.replace("'", "\\'");
+                variable += "['" + idx + "']";
+            }
+        }
+    }
+
+    var vals = stack[stack.length-1]['vals'].slice(0, 2);
+    var vars = [
+        variable.replace('$Foo',     'got'),
+        variable.replace('$Foo',     'expected')
+    ];
+
+    var out = "Structures begin differing at:" + Test.Builder.LF;
+    for (var i = 0; i < vals.length; i++) {
+        var val = vals[i];
+        if (val == null) {
+            val = 'undefined';
+        } else {
+             val == Test.More.DNE ? "Does not exist" : "'" + val + "'";
+        }
+    }
+
+    out += vars[0] + ' = ' + vals[0] + Test.Builder.LF;
+    out += vars[1] + ' = ' + vals[1] + Test.Builder.LF;
+    
+    return '    ' + out;
+};
+
+/* Commented out per suggestion from Michael Schwern. It turned out to be
+   confusing to Test::More users because it isn't atually a test. Use
+   isDeeply() instead and don't worry about it.
+
+Test.More.eqArray = function (a1, a2) {
+    if (!isa(a1, 'Array') || !isa(a2, 'Array')) {
+        Test.More.Test.warn("Non-array passed to eqArray()");
+        return false;
+    }
+    return Test.More._eqArray(a1, a2, [], []);
+};
+
+*/
+
+Test.More._eqArray = function (a1, a2, stack, seen) {
+    // Return if they're the same object.
+    if (a1 == a2) return true;
+
+    // JavaScript objects have no unique identifiers, so we have to store
+    // references to them all in an array, and then compare the references
+    // directly. It's slow, but probably won't be much of an issue in
+    // practice. Start by making a local copy of the array to as to avoid
+    // confusing a reference seen more than once (such as [a, a]) for a
+    // circular reference.
+    for (var j = 0; j < seen.length; j++) {
+        if (seen[j][0] == a1) {
+            return seen[j][1] == a2;
+        }
+    }
+
+    // If we get here, we haven't seen a1 before, so store it with reference
+    // to a2.
+    seen.push([ a1, a2 ]);
+
+    var ok = true;
+    // Only examines enumerable attributes. Only works for numeric arrays!
+    // Associative arrays return 0. So call _eqAssoc() for them, instead.
+    var max = a1.length > a2.length ? a1.length : a2.length;
+    if (max == 0) return Test.More._eqAssoc(a1, a2, stack, seen);
+    for (var i = 0; i < max; i++) {
+        var e1 = i > a1.length - 1 ? Test.More.DNE : a1[i];
+        var e2 = i > a2.length - 1 ? Test.More.DNE : a2[i];
+        stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
+        if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
+            stack.pop();
+        } else {
+            break;
+        }
+    }
+    return ok;
+};
+
+/* Commented out per suggestion from Michael Schwern. It turned out to be
+   confusing to Test::More users because it isn't atually a test. Use
+   isDeeply() instead and don't worry about it.
+
+Test.More.eqHash = function () {
+    return eqAssoc.apply(this, arguments);
+};
+
+Test.More.eqAssoc = function (o1, o2) {
+    if (typeof o1 != "object" || typeof o2 != "object") {
+        Test.More.Test.warn("Non-object passed to eqAssoc()");
+        return false;
+    } else if (   (isa(o1, 'Array') && o1.length > 0)
+               || (isa(o2, 'Array') && o2.length > 0))
+    {
+        Test.More.Test.warn("Ordered array passed to eqAssoc()");
+        return false;
+    }
+    return Test.More._eqAssoc(o1, o2, [], []);
+};
+
+*/
+
+Test.More._eqAssoc = function (o1, o2, stack, seen) {
+    // Return if they're the same object.
+    if (o1 == o2) return true;
+
+    // JavaScript objects have no unique identifiers, so we have to store
+    // references to them all in an array, and then compare the references
+    // directly. It's slow, but probably won't be much of an issue in
+    // practice. Start by making a local copy of the array to as to avoid
+    // confusing a reference seen more than once (such as [a, a]) for a
+    // circular reference.
+    seen = seen.slice(0);
+    for (var j = 0; j < seen.length; j++) {
+        if (seen[j][0] == o1) {
+            return seen[j][1] == o2;
+        }
+    }
+
+    // If we get here, we haven't seen o1 before, so store it with reference
+    // to o2.
+    seen.push([ o1, o2 ]);
+
+    // They should be of the same class.
+
+    var ok = true;
+    // Only examines enumerable attributes.
+    var o1Size = 0; for (var i in o1) o1Size++;
+    var o2Size = 0; for (var i in o2) o2Size++;
+    var bigger = o1Size > o2Size ? o1 : o2;
+    for (var i in bigger) {
+        var e1 = o1[i] == undefined ? Test.More.DNE : o1[i];
+        var e2 = o2[i] == undefined ? Test.More.DNE : o2[i];
+        stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
+        if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
+            stack.pop();
+        } else {
+            break;
+        }
+    }
+    return ok;
+};
+
+Test.More._eqSet = function (a1, a2, stack, seen) {
+    return Test.More._eqArray(a1.slice(0).sort(), a2.slice(0).sort(), stack, seen);
+};
+
+Test.More.isSet = function (a1, a2, desc) {
+    var stack = [], seen = [], ok = true;
+    if (Test.More._eqSet(a1, a2, stack, seen)) {
+        ok = Test.More.Test.ok(true, desc);
+    } else {
+        ok = Test.More.Test.ok(false, desc);
+        Test.More.Test.diag(Test.More._formatStack(stack));
+    }
+    return ok;
+};
+
+Test.More.isa = function (object, clas) {
+    return Test.Builder.typeOf(object) == clas;
+};
+
+// Handle exporting.
+if (typeof JSAN == 'undefined') Test.Builder.exporter(Test.More);
diff --git a/test/lib/Test/Simple.js b/test/lib/Test/Simple.js
new file mode 100644 (file)
index 0000000..8222ea8
--- /dev/null
@@ -0,0 +1,29 @@
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+
+// Set up package.
+if (typeof JSAN != 'undefined') new JSAN().use('Test.Builder');
+else {
+    if (typeof Test == 'undefined' || typeof Test.Builder == 'undefined')
+        throw new Error(
+            "You must load either JSAN or Test.Builder "
+            + "before loading Test.Simple"
+        );
+}
+
+Test.Simple = {};
+Test.Simple.EXPORT      = ['plan', 'ok'];
+Test.Simple.EXPORT_TAGS = { ':all': Test.Simple.EXPORT };
+Test.Simple.VERSION     = '0.11';
+
+Test.Simple.plan = function (cmds) {
+    return Test.Simple.Test.plan(cmds);
+};
+
+Test.Simple.ok = function (val, desc) {
+    return Test.Simple.Test.ok(val, desc);
+};
+
+// Handle exporting.
+if (typeof JSAN == 'undefined') Test.Builder.exporter(Test.Simple);
+
+Test.Simple.Test = new Test.Builder();
diff --git a/test/lib/Test/tmp.js b/test/lib/Test/tmp.js
new file mode 100644 (file)
index 0000000..c2d98b8
--- /dev/null
@@ -0,0 +1,1662 @@
+// # $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!");
+
+    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]];
+    }
+};// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+// Create a namespace for ourselves.
+
+// Set up package.
+if (typeof JSAN != 'undefined') new JSAN().use('Test.Builder');
+else {
+    if (typeof Test == 'undefined' || typeof Test.Builder == 'undefined')
+        throw new Error(
+            "You must load either JSAN or Test.Builder "
+            + "before loading Test.More"
+        );
+}
+
+Test.More = {};
+Test.More.EXPORT = [
+    'plan',
+    'ok', 'is', 'isnt',
+    'like', 'unlike',
+    'cmpOK', 'canOK', 'isaOK',
+    'pass', 'fail', 'diag', 'loadOK',
+    'skip', 'todo', 'todoSkip', 'skipRest',
+    'isDeeply', 'isSet', 'isa'
+];
+Test.More.EXPORT_TAGS = { ':all': Test.More.EXPORT };
+Test.More.VERSION     = '0.11';
+
+Test.More.ShowDiag = true;
+Test.Builder.DNE = { dne: 'Does not exist' };
+Test.More.Test = new Test.Builder();
+Test.More.builder = function () { return Test.More.Test; };
+
+Test.More.plan = function (cmds) {
+    if (cmds.noDiag) {
+        Test.More.ShowDiag = false;
+        delete cmds.noDiag;
+    }
+    return Test.More.Test.plan.apply(Test.More.Test, [cmds]);
+};
+
+Test.More.ok = function (test, desc) {
+    return Test.More.Test.ok(test, desc);
+};
+
+Test.More.is = function (got, expect, desc) {
+    return Test.More.Test.isEq(got, expect, desc);
+};
+
+Test.More.isnt = function (got, expect, desc) {
+    return Test.More.Test.isntEq(got, expect, desc);
+};
+
+Test.More.like = function (val, regex, desc) {
+    return Test.More.Test.like(val, regex, desc);
+};
+
+Test.More.unlike = function (val, regex, desc) {
+    return Test.More.Test.unlike(val, regex, desc);
+};
+
+Test.More.cmpOK = function (got, op, expect, desc) {
+    return Test.More.Test.cmpOK(got, op, expect, desc);
+};
+
+Test.More.canOK = function (proto) {
+    var ok;
+    // Make sure they passed some method names for us to check.
+    if (!arguments.length > 1) {
+        ok = Test.More.Test.ok(false, clas + '.can(...)');
+        Test.More.Test.diag('    canOK() called with no methods');
+        return ok;
+    }
+
+    // Get the class name and the prototype.
+    var clas;
+    if (typeof proto == 'string') {
+        // We just have a class name.
+        clas = proto;
+        proto = eval(clas + '.prototype');
+    } else {
+        // We have an object or something that can be converted to an object.
+        clas = Test.Builder.typeOf(proto);
+        proto = proto.constructor.prototype;
+    }
+
+    var nok = [];
+    for (var i = 1; i < arguments.length; i++) {
+        var method = arguments[i];
+        if (typeof proto[method] != 'function') nok.push(method);
+    }
+
+    // There'es no can() method in JavaScript, but what the hell!
+    var desc = clas + ".can('" + (arguments.length == 2 ? arguments[1] : '...') + "')";
+    ok = Test.More.Test.ok(!nok.length, desc);
+    for (var i = 0; i < nok.length; i++) {
+        Test.More.Test.diag('    ' + clas + ".can('" + nok[i] + "') failed");
+    }
+    return ok;
+};
+
+Test.More.isaOK = function (object, clas, objName) {
+    var mesg;
+    if (objName == null) objName = 'The object';
+    var name = objName + ' isa ' + clas;
+    if (object == null) {
+        mesg = objName + " isn't defined";
+    } else if (!Test.More._isRef(object)) {
+        mesg = objName + " isn't a reference";
+    } else {
+        var ctor = eval(clas);
+        if (Object.isPrototypeOf) {
+            // With JavaScript 1.5, we can determine inheritance.
+            if (!ctor.prototype.isPrototypeOf(object)) {
+                mesg = objName + " isn't a '" + clas + "' it's a '"
+                  + Test.Builder.typeOf(object) + "'";
+            }
+        } else {
+            // We can just determine what constructor was used. This will
+            // not work for inherited constructors.
+            if (object.constructor != ctor)
+                mesg = objName + " isn't a '" + clas + "' it's a '"
+                  + Test.Builder.typeOf(object) + '"';
+        }
+    }
+    
+    var ok;
+    if (mesg) {
+        ok = Test.More.Test.ok(false, name);
+        Test.More.Test.diag('    ' + mesg);
+    } else {
+        ok = Test.More.Test.ok(true, name);
+    }
+
+    return ok;
+};
+
+Test.More.pass = function (name) {
+    return Test.More.Test.ok(true, name);
+};
+
+Test.More.fail = function (name) {
+    return Test.More.Test.ok(false, name);
+};
+
+Test.More.diag = function () {
+    if (!Test.More.ShowDiag) return;
+    return Test.More.Test.diag.apply(Test.More.Test, arguments);
+};
+
+// Use this instead of use_ok and require_ok.
+Test.More.loadOK = function () {
+    // XXX What do I do here? Eval?
+    // XXX Just always fail for now, to keep people from using it just yet.
+    return false;
+};
+
+Test.More.skip = function (why, howMany) {
+    if (howMany == null) {
+        if (!Test.Builder.NoPlan)
+            Test.More.Test.warn("skip() needs to know howMany tests are in the block");
+        howMany = 1;
+    }
+    for (i = 0; i < howMany; i++) {
+        Test.More.Test.skip(why);
+    }
+};
+
+Test.More.todo = function (why, howMany) {
+    if (howMany == null) {
+        if (!Test.Builder.NoPlan)
+            Test.More.Test.warn("todo() needs to know howMany tests are in the block");
+        howMany = 1;
+    }
+    return Test.More.Test.todo(why, howMany);
+};
+
+Test.More.todoSkip = function (why, howMany) {
+    if (howMany == null) {
+        if (!Test.Builder.NoPlan)
+            Test.More.Test.warn("todoSkip() needs to know howMany tests are in the block");
+        howMany = 1;
+    }
+
+    for (i = 0; i < howMany; i++) {
+        Test.More.Test.todoSkip(why);
+    }
+};
+
+Test.More.skipRest = function (why) {
+    Test.More.Test.skipRest(why);
+};
+
+Test.More.isDeeply = function (it, as, name) {
+    if (arguments.length != 2 && arguments.length != 3) {
+        Test.More.Test.warn(
+            'isDeeply() takes two or three args, you gave '
+            + arguments.length + "."
+        );
+    }
+
+    var ok;
+    // ^ is the XOR operator.
+    if (Test.More._isRef(it) ^ Test.More._isRef(as)) {
+        // One's a reference, one isn't.
+        ok = false;
+    } else if (!Test.More._isRef(it) && !Test.More._isRef(as)) {
+        // Neither is an object.
+        ok = Test.More.Test.isEq(it, as, name);
+    } else {
+        // We have two objects. Do a deep comparison.
+        var stack = [], seen = [];
+        if ( Test.More._deepCheck(it, as, stack, seen)) {
+            ok = Test.More.Test.ok(true, name);
+        } else {
+            ok = Test.More.Test.ok(false, name);
+            Test.More.Test.diag(Test.More._formatStack(stack));
+        }
+    }
+    return ok;
+};
+
+Test.More._deepCheck = function (e1, e2, stack, seen) {
+    var ok = false;
+    // Either they're both references or both not.
+    var sameRef = !(!Test.More._isRef(e1) ^ !Test.More._isRef(e2));
+    if (e1 == null && e2 == null) {
+        ok = true;
+    } else if (e1 != null ^ e2 != null) {
+        ok = false;
+    } else if (e1 == Test.More.DNE ^ e2 == Test.More.DNE) {
+        ok = false;
+    } else if (sameRef && e1 == e2) {
+        // Handles primitives and any variables that reference the same
+        // object, including functions.
+        ok = true;
+    } else if (isa(e1, 'Array') && isa(e2, 'Array')) {
+        ok = Test.More._eqArray(e1, e2, stack, seen);
+    } else if (typeof e1 == "object" && typeof e2 == "object") {
+        ok = Test.More._eqAssoc(e1, e2, stack, seen);
+    } else {
+        // If we get here, they're not the same (function references must
+        // always simply rererence the same function).
+        stack.push({ vals: [e1, e2] });
+        ok = false;
+    }
+    return ok;
+};
+
+Test.More._isRef = function (object) {
+    var type = typeof object;
+    return type == 'object' || type == 'function';
+};
+
+Test.More._formatStack = function (stack) {
+    var variable = '$Foo';
+    for (var i = 0; i < stack.length; i++) {
+        var entry = stack[i];
+        var type = entry['type'];
+        var idx = entry['idx'];
+        if (idx != null) {
+            if (/^\d+$/.test(idx)) {
+                // Numeric array index.
+                variable += '[' + idx + ']';
+            } else {
+                // Associative array index.
+                idx = idx.replace("'", "\\'");
+                variable += "['" + idx + "']";
+            }
+        }
+    }
+
+    var vals = stack[stack.length-1]['vals'].slice(0, 2);
+    var vars = [
+        variable.replace('$Foo',     'got'),
+        variable.replace('$Foo',     'expected')
+    ];
+
+    var out = "Structures begin differing at:" + Test.Builder.LF;
+    for (var i = 0; i < vals.length; i++) {
+        var val = vals[i];
+        if (val == null) {
+            val = 'undefined';
+        } else {
+             val == Test.More.DNE ? "Does not exist" : "'" + val + "'";
+        }
+    }
+
+    out += vars[0] + ' = ' + vals[0] + Test.Builder.LF;
+    out += vars[1] + ' = ' + vals[1] + Test.Builder.LF;
+    
+    return '    ' + out;
+};
+
+/* Commented out per suggestion from Michael Schwern. It turned out to be
+   confusing to Test::More users because it isn't atually a test. Use
+   isDeeply() instead and don't worry about it.
+
+Test.More.eqArray = function (a1, a2) {
+    if (!isa(a1, 'Array') || !isa(a2, 'Array')) {
+        Test.More.Test.warn("Non-array passed to eqArray()");
+        return false;
+    }
+    return Test.More._eqArray(a1, a2, [], []);
+};
+
+*/
+
+Test.More._eqArray = function (a1, a2, stack, seen) {
+    // Return if they're the same object.
+    if (a1 == a2) return true;
+
+    // JavaScript objects have no unique identifiers, so we have to store
+    // references to them all in an array, and then compare the references
+    // directly. It's slow, but probably won't be much of an issue in
+    // practice. Start by making a local copy of the array to as to avoid
+    // confusing a reference seen more than once (such as [a, a]) for a
+    // circular reference.
+    for (var j = 0; j < seen.length; j++) {
+        if (seen[j][0] == a1) {
+            return seen[j][1] == a2;
+        }
+    }
+
+    // If we get here, we haven't seen a1 before, so store it with reference
+    // to a2.
+    seen.push([ a1, a2 ]);
+
+    var ok = true;
+    // Only examines enumerable attributes. Only works for numeric arrays!
+    // Associative arrays return 0. So call _eqAssoc() for them, instead.
+    var max = a1.length > a2.length ? a1.length : a2.length;
+    if (max == 0) return Test.More._eqAssoc(a1, a2, stack, seen);
+    for (var i = 0; i < max; i++) {
+        var e1 = i > a1.length - 1 ? Test.More.DNE : a1[i];
+        var e2 = i > a2.length - 1 ? Test.More.DNE : a2[i];
+        stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
+        if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
+            stack.pop();
+        } else {
+            break;
+        }
+    }
+    return ok;
+};
+
+/* Commented out per suggestion from Michael Schwern. It turned out to be
+   confusing to Test::More users because it isn't atually a test. Use
+   isDeeply() instead and don't worry about it.
+
+Test.More.eqHash = function () {
+    return eqAssoc.apply(this, arguments);
+};
+
+Test.More.eqAssoc = function (o1, o2) {
+    if (typeof o1 != "object" || typeof o2 != "object") {
+        Test.More.Test.warn("Non-object passed to eqAssoc()");
+        return false;
+    } else if (   (isa(o1, 'Array') && o1.length > 0)
+               || (isa(o2, 'Array') && o2.length > 0))
+    {
+        Test.More.Test.warn("Ordered array passed to eqAssoc()");
+        return false;
+    }
+    return Test.More._eqAssoc(o1, o2, [], []);
+};
+
+*/
+
+Test.More._eqAssoc = function (o1, o2, stack, seen) {
+    // Return if they're the same object.
+    if (o1 == o2) return true;
+
+    // JavaScript objects have no unique identifiers, so we have to store
+    // references to them all in an array, and then compare the references
+    // directly. It's slow, but probably won't be much of an issue in
+    // practice. Start by making a local copy of the array to as to avoid
+    // confusing a reference seen more than once (such as [a, a]) for a
+    // circular reference.
+    seen = seen.slice(0);
+    for (var j = 0; j < seen.length; j++) {
+        if (seen[j][0] == o1) {
+            return seen[j][1] == o2;
+        }
+    }
+
+    // If we get here, we haven't seen o1 before, so store it with reference
+    // to o2.
+    seen.push([ o1, o2 ]);
+
+    // They should be of the same class.
+
+    var ok = true;
+    // Only examines enumerable attributes.
+    var o1Size = 0; for (var i in o1) o1Size++;
+    var o2Size = 0; for (var i in o2) o2Size++;
+    var bigger = o1Size > o2Size ? o1 : o2;
+    for (var i in bigger) {
+        var e1 = o1[i] == undefined ? Test.More.DNE : o1[i];
+        var e2 = o2[i] == undefined ? Test.More.DNE : o2[i];
+        stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
+        if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
+            stack.pop();
+        } else {
+            break;
+        }
+    }
+    return ok;
+};
+
+Test.More._eqSet = function (a1, a2, stack, seen) {
+    return Test.More._eqArray(a1.slice(0).sort(), a2.slice(0).sort(), stack, seen);
+};
+
+Test.More.isSet = function (a1, a2, desc) {
+    var stack = [], seen = [], ok = true;
+    if (Test.More._eqSet(a1, a2, stack, seen)) {
+        ok = Test.More.Test.ok(true, desc);
+    } else {
+        ok = Test.More.Test.ok(false, desc);
+        Test.More.Test.diag(Test.More._formatStack(stack));
+    }
+    return ok;
+};
+
+Test.More.isa = function (object, clas) {
+    return Test.Builder.typeOf(object) == clas;
+};
+
+// Handle exporting.
+if (typeof JSAN == 'undefined') Test.Builder.exporter(Test.More);
+// # $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 _player != 'undefined'){
+    //Director
+    if (typeof _global.Test != "object") _global.Test = {PLATFORM: 'director'};
+    else _global.Test.PLATFORM = 'director';
+} else {
+    throw new Error("Test.Harness does not support your platform");
+}
+
+Test.Harness = function () {};
+Test.Harness.VERSION = '0.11';
+Test.Harness.Done = 0;
+
+// Stoopid IE.
+Test.Harness.LF = typeof document != "undefined"
+                  && typeof document.all != "undefined"
+  ? "\r"
+  : "\n";
+
+Test.Harness.prototype.isDone = Test.Harness.isDone;
+
+/*
+
+    bonus           Number of individual todo tests unexpectedly passed
+    ran             Number of individual tests ran
+    ok              Number of individual tests passed
+    subSkipped      Number of individual tests skipped
+    todo            Number of individual todo tests
+
+    files           Number of test files ran
+    good            Number of test files passed
+    bad             Number of test files failed
+    tests           Number of test files originally given
+    skipped         Number of test files skipped
+
+*/
+
+Test.Harness.prototype.bonus      = 0;
+Test.Harness.prototype.ran        = 0;
+Test.Harness.prototype.ok         = 0;
+Test.Harness.prototype.subSkipped = 0;
+Test.Harness.prototype.todo       = 0;
+Test.Harness.prototype.files      = 0;
+Test.Harness.prototype.good       = 0;
+Test.Harness.prototype.bad        = 0;
+Test.Harness.prototype.tests      = 0;
+Test.Harness.prototype.skipped    = 0;
+Test.Harness.prototype.failures   = [];
+
+Test.Harness.runTests = function () {
+    // XXX Can't handle inheritance, right? Or can we?
+    var harness = new Test.Harness();
+    harness.runTests.apply(harness, arguments);
+};
+
+Test.Harness.prototype.outFileNames = function (files) {
+    var len = 0;
+    for (var i = 0; i < files.length; i++) {
+        if (files[i].length > len) len = files[i].length;
+    }
+    len += 3;
+    var ret = [];
+    for (var i = 0; i < files.length; i++) {
+        var outName = files[i];
+        var add = len - files[i].length;
+        // Where is Perl's x operator when I need it??
+        for (var j = 0; j < add; j++) {
+            outName += '.';
+        }
+        ret.push(outName);
+    }
+    return ret;
+};
+
+Test.Harness.prototype.outputResults = function (test, file, fn, attrs) {
+    this.tests++;
+    this.ran += test.TestResults.length;
+    var fails = [];
+    var track = {
+      fn:       file,
+      total:    test.expectedTests,
+      ok:       0,
+      failList: []
+    };
+
+    if (test.TestResults.length) {
+        this.files++;
+        var pass = true;
+        for (var i = 0; i < test.TestResults.length; i++) {
+            var ok = "ok";
+            if (test.TestResults[i].ok) {
+                this.ok++;
+                track.ok++
+                if (test.TestResults[i].type == 'todo') {
+                    // Handle unexpected pass.
+                    if (test.TestResults[i].actualOK) this.bonus++;
+                    this.todo ++;
+                } else if (test.TestResults[i].type == 'skip') this.subSkipped++;
+            } else {
+                if (test.TestResults[i].type == 'todo') {
+                    // Expected failure.
+                    this.todo++;
+                } else {
+                    pass = false;
+                    track.failList.push(i + 1);
+                }
+                ok = "not ok"; // XXX Need to handle TODO and TODO Skipped.
+            }
+            
+            if (!pass || attrs.verbose) fn(test.TestResults[i].output);
+        }
+        
+        if (pass) {
+            this.good++;
+            fn("ok" + Test.Harness.LF);
+        } else {
+            this.bad++;
+            var err = "NOK # Failed ";
+            if (track.failList.length == 1) {
+                err += "test " + track.failList[0];
+            } else {
+                err += "tests " + this._failList(track.failList);
+            }
+            fn(err + " in " + file + Test.Harness.LF);
+        }
+    } else if (test.SkipAll){
+        // All tests skipped.
+        this.skipped++;
+        this.good++;
+        fn("1..0 # Skip 1" + Test.Harness.LF);
+    } else {
+        // Wha happened? Tests ran, but no results!
+        this.files++;
+        this.bad++;
+        fn("FAILED before any test output arrived" + Test.Harness.LF);
+    }
+    if (track.failList.length) this.failures.push(track);
+};
+
+Test.Harness.prototype._allOK = function () {
+    return this.bad == 0 && (this.ran || this.skipped) ? true : false;
+};
+
+Test.Harness.prototype.outputSummary = function (fn, time) {
+    var bonusmsg = this._bonusmsg();
+    var pct;
+    if (this._allOK()) {
+        fn("All tests successful" + bonusmsg + '.' + Test.Harness.LF);
+    } else if (!this.tests) {
+        fn("FAILED—no tests were run for some reason." + Test.Harness.LF);
+    } else if (!this.ran) {
+        var blurb = this.tests == 1 ? "file" : "files";
+        fn("FAILED—" + this.tests + " test " + blurb + " could be run, "
+           + "alas—no output ever seen." + Test.Harness.LF);
+    } else {
+        pct = this.good / this.tests * 100;
+        var pctOK = 100 * this.ok / this.ran;
+        var subpct = (this.ran - this.ok) + "/" + this.ran
+          + " subtests failed, " + pctOK.toPrecision(4) + "% okay.";
+
+        if (this.bad) {
+            bonusmsg = bonusmsg.replace(/^,?\s*/, '');
+            if (bonusmsg) fn(bonusmsg + '.' + Test.Harness.LF);
+            fn("Failed " + this.bad + "/" + this.tests + " test scripts, "
+               + pct.toPrecision(4) + "% okay. " + subpct + Test.Harness.LF);
+        }
+        this.formatFailures(fn);
+    }
+
+    fn("Files=" + this.tests + ", Tests=" + this.ran + ", " + (time / 1000)
+       + " seconds" + Test.Harness.LF);
+};
+
+Test.Harness.prototype.formatFailures = function () {
+    var table = '';
+    var failedStr = "Failed Test";
+    var middleStr = " Total Fail  Failed  ";
+    var listStr = "List of Failed";
+    var cols = 80;
+
+    // Figure out our longest name string for formatting purposes.
+    var maxNamelen = failedStr.length;
+    for (var i = 0; i < this.failures.length; i++) {
+        var len = this.failures[i].length;
+        if (len > maxNamelen) maxNamelen = len;
+    }
+
+    var listLen = cols - middleStr.length - maxNamelen.length;
+    if (listLen < listStr.length) {
+        listLen = listStr.length;
+        maxNamelen = cols - middleStr.length - listLen;
+        if (maxNamelen < failedStr.length) {
+            maxNamelen = failedStr.length;
+            cols = maxNamelen + middleStr.length + listLen;
+        }
+    }
+
+    var out = failedStr;
+    if (out.length < maxNamelen) {
+        for (var j = out.length; j < maxNameLength; j++) {
+            out += ' ';
+        }
+    }
+    out += '  ' + middleStr;
+    // XXX Need to finish implementing the text-only version of the failures
+    // table.
+};
+
+Test.Harness.prototype._bonusmsg = function () {
+    var bonusmsg = '';
+    if (this.bonus) {
+        bonusmsg = (" (" + this.bonus + " subtest" + (this.bonus > 1 ? 's' : '')
+          + " UNEXPECTEDLY SUCCEEDED)");
+    }
+
+    if (this.skipped) {
+        bonusmsg += ", " + this.skipped + " test"
+          + (this.skipped != 1 ? 's' : '');
+        if (this.subSkipped) {
+            bonusmsg += " and " + this.subSkipped + " subtest"
+              + (this.subSkipped != 1 ? 's' : '');
+        }
+        bonusmsg += ' skipped';
+    } else if (this.subSkipped) {
+        bonusmsg += ", " + this.subSkipped + " subtest"
+          + (this.subSkipped != 1 ? 's' : '') + " skipped";
+    }
+
+    return bonusmsg;
+}
+
+Test.Harness.prototype._failList = function (fails) {
+    var last = -1;
+    var dash = '';
+    var list = [];
+    for (var i = 0; i < fails.length; i++) {
+        if (dash) {
+            // We're in a series of numbers.
+            if (fails[i] - 1 == last) {
+                // We're still in it.
+                last = fails[i];
+            } else {
+                // End of the line.
+                list[list.length-1] += dash + last;
+                last = -1;
+                list.push(fails[i]);
+                dash = '';
+            }
+        } else if (fails[i] - 1 == last) {
+            // We're in a new series.
+            last = fails[i];
+            dash = '-';
+        } else {
+            // Not in a sequence.
+            list.push(fails[i]);
+            last = fails[i];
+        }
+    }
+    if (dash) list[list.length-1] += dash + last;
+    return list.join(' ');
+}
+// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
+
+if (typeof JSAN != 'undefined') new JSAN().use('Test.Harness');
+
+Test.Harness.Browser = function () {};
+Test.Harness.Browser.VERSION = '0.11';
+
+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;
+    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/.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;
+};
+
+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);
+    return function (msg) {
+        node.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 () {
+    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 node = document.getElementById('output');
+    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(outfiles[ti] + (harness.args.verbose ? Test.Harness.LF : ''));
+            buffer.location.href = files[ti];
+            runNext();
+        } else {
+            harness.outputSummary(
+                summaryOutput,
+                new Date() - start
+             );
+            finish();
+        }
+    };
+
+    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 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.
+            if (Test.Harness.Done > 0
+                && Test.Harness.Done >= buffer.Test.Builder.Instances.length)
+            {
+                Test.Harness.Done = 0;
+                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(outfiles[ti] + (this.args.verbose ? Test.Harness.LF : ''));
+    start = new Date();
+    buffer.location.href = files[ti]; // replace() doesn't seem to work.
+};
+
+// From "JavaScript: The Difinitive Guide 4ed", p 214.
+Test.Harness.Browser.prototype.args = {};
+var pairs = location.search.substring(1).split(",");
+for (var i = 0; i < pairs.length; i++) {
+    var pos = pairs[i].indexOf('=');
+    if (pos == -1) continue;
+    var key = pairs[i].substring(0, pos);
+    var val = pairs[i].substring(pos + 1); 
+    if (Test.Harness.Browser.prototype.args[key]) {
+        if (typeof Test.Harness.Browser.prototype.args[key] == 'string') {
+            Test.Harness.Browser.prototype.args[key] =
+                [Test.Harness.Browser.prototype.args[key]];
+        }
+        Test.Harness.Browser.prototype.args[key].push(unescape(val));
+    } else {
+        Test.Harness.Browser.prototype.args[key] = unescape(val);
+    }
+}
+delete pairs;
+
+Test.Harness.Browser.prototype.formatFailures = function (fn) {
+    // XXX append new element for table and then populate it.
+    var failedStr = "Failed Test";
+    var middleStr = " Total Fail  Failed  ";
+    var listStr = "List of Failed";
+    var table = '<table style=""><tr><th>Failed Test</th><th>Total</th>'
+      + '<th>Fail</th><th>Failed</th></tr>';
+    for (var i = 0; i < this.failures.length; i++) {
+        var track = this.failures[i];
+        table += '<tr><td>' + track.fn + '</td>'
+          + '<td>' + track.total + '</td>'
+          + '<td>' + track.total - track.ok + '</td>'
+          + '<td>' + this._failList(track.failList) + '</td></tr>'
+    };
+    table += '</table>' + Test.Harness.LF;
+    var node = document.getElementById('summary');
+    node.innerHTML += table;
+    window.scrollTo(0, document.body.offsetHeight || document.body.scrollHeight);
+};
diff --git a/test/lib/Test/tmp2.js b/test/lib/Test/tmp2.js
new file mode 100644 (file)
index 0000000..a89609b
--- /dev/null
@@ -0,0 +1,2 @@
+eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[(function(e){return d[e]})];e=(function(){return'\\w+'});c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('7(I 4T!=\'N\'){7(I 5==\'N\')5={1q:\'2r\'};m 5.1q=\'2r\'}m 7(I 1P!=\'N\'){7(I 1P.5=="N")1P.5={1q:\'2z\'};m 1P.5.1q=\'2z\'}m{1Y T 1E("5.h 4W 2f 4Q 2s 4X")}5.b=e(){7(!5.b.5){5.b.5=6.4L();5.b.2w.10(6)}f 5.b.5};5.b.3A=\'0.11\';5.b.2w=[];5.b.2o=/\\r?\\n|\\r/g;5.b.37={3I:\'==\',8f:\'!=\',8g:\'<\',82:\'>\',8h:\'>=\',8j:\'<=\'};5.b.v=I C!="N"&&I C.3K!="N"?"\\r":"\\n";5.b.1n=e(y){1Y T 1E(y)};5.b.3b=e(60,q){7(!60)f;5.b.1n("8k! "+q+5.b.v+ +"8l 8m 8n 6T! 62 63 31 64 "+"65!")};5.b.2q=e(U){9 c=2m.l.1t.20(U);9 16=c.3q(8,c.p-1);7(16!=\'2m\')f 16;7(/e ([^(\\s]+)/.u(66.1t.5Z(U.4l))){f 3l.$1}f 16};5.b.68=e(){9 u=5.b.5;5.b.5=z;9 2N=T 5.b();5.b.5=u;f 2N.4L()};5.b.l.4L=e(){6.3p=K;6.1C=K;6.1N=K;6.L=0;6.1a=0;6.4e=G;6.4G=K;6.3L=K;6.E=[];6.2e=[];6.1A=[];6.2F=[0];6.46=0;f 6.39()};5.b.l.1Q=e(y){6.12().5Z(6,y)};5.b.l.32=e(y){6.2R().20(6,15)};5.b.l.1b=e(1m){7(!1m)f;7(6.1C)5.b.1n("1y 2I 1o 1b 69!");7(!(1m 8i 2m))5.b.1n("1b() 3r\'t 4P "+1m);B(9 1s 1B 1m){7(1s==\'P\'){7(1m[1s]==z){6a.1n("6b 6c N 2J 26 P. 2u 1u 1W 2I 1o "+"8e 6f 8c P 1W 1b 1o 1G 3W 6g a 8a."+5.b.v)}m 7(!1m[1s]){5.b.1n("1y 6i 1o 1G 0 P! 1y\'6j w 1o 1G 6k."+5.b.v)}m{6.3N(1m[1s])}}m 7(1s==\'4t\'){6.4t(1m[1s])}m 7(1s==\'3a\'&&1m[1s]){6.3a()}m{5.b.1n("1b() 3r\'t 4P "+1s+(1m[1s]?(" "+1m[1s]):\'\'))}}};5.b.l.3N=e(1J){7(1J){7(6m(1J)){5.b.1n("2W 26 P 5x 5p a 6o 6p. 1y 5P 1D \'"+1J+"\'."+5.b.v)}6.1a=1J.4V();6.1C=1;7(!6.28())6.1Q("1.."+1J+5.b.v)}f 6.1a};5.b.l.3a=e(){6.1N=1;6.1C=1};5.b.l.6q=e(){7(6.1a)f 6.1a;7(6.1N)f\'3a\'};5.b.l.4t=e(V){9 x="1..0";7(V)x+=" # 3T "+V;x+=5.b.v;6.3S=1;7(!6.28())6.1Q(x);F.36=e(){f G}1Y T 1E("6t")};5.b.l.k=e(u,q){u=!!u;7(!6.1C)5.b.1n("1y 2I 1o 1G a u 2L a 1b! 3X 3Y a 1b.");6.L++;7(q)q=q.1t();9 7V 7(q!=z&&/^[\\d\\s]+$/.u(q)){6.1c("6u u 7T 4c \'"+q+"\'. 1y 6w\'t 4F",5.b.v,"7R B 2s u 6x. 7P 6A.")}9 S=6.57();9 x=\'\';9 1i={};7(u){1i.k=G;1i.2P=u}m{x+=\'2f \';1i.k=S?G:K;1i.2P=K}x+=\'k\';7(6.38)x+=\' \'+6.L;7(q==z){1i.q=\'\'}m{q=q.1z(5.b.2o,5.b.v+"# ");q.5M(\'#\').35(\'\\\\#\');x+=\' - \'+q;1i.q=q}7(S){S=S.1z(5.b.2o,5.b.v+"# ");x+=" # 3F "+S;1i.V=S;1i.Z=\'S\'}m{1i.V=\'\';1i.Z=\'\'}6.E[6.L-1]=1i;x+=5.b.v;6.1Q(x);7(!u){9 y=S?"1p (3F)":"1p";6.1c("    "+y+" u")}1i.12=6.1A.3k(0).35(\'\');f u};5.b.l.4y=e(w,H,q){7(w==z||H==z){f 6.2H(w,\'==\',H,q)}f 6.1T(w,\'==\',H,q)};5.b.l.6B=e(w,H,q){7(w==z||H==z){f 6.2H(w,\'==\',H,q)}f 6.1T(2W(w),\'==\',2W(H),q)};5.b.l.5B=e(w,1M,q){7(w==z||1M==z){f 6.2H(w,\'!=\',1M,q)}f 6.1T(w,\'!=\',1M,q)};5.b.l.6D=e(w,1M,q){7(w==z||1M==z){f 6.2H(w,\'!=\',1M,q)}f 6.1T(2W(w),\'!=\',2W(1M),q)};5.b.l.1u=e(O,W,q){f 6.3G(O,W,\'=~\',q)};5.b.l.3s=e(O,W,q){f 6.3G(O,W,\'!~\',q)};5.b.l.3G=e(O,W,2b,q){9 Z=5.b.2q(W);9 k;7(Z.7H()==\'2T\'){W=T 3l(W)}m{7(Z!=\'3l\'){k=6.k(K,q);6.1c("\'"+W+"\' 3r\'t 6E 7F 1u a W 1o 6F.");f k}}7(O==z||I O!=\'2T\'){7(2b==\'=~\'){k=6.k(K,q);6.3H(O,W,2b)}m{k=6.k(G,q)}f k}9 u=O.3e(W);7(2b==\'!~\')u=!u;k=6.k(u,q);7(!k)6.3H(O,W,2b);f k};5.b.l.3H=e(O,W,2b){9 3e=2b==\'=~\'?"3r\'t 3e":"      6H";f 6.1c("                  \'"+O+""+5.b.v+"    "+3e+" /"+W.6J+"/")};5.b.l.1T=e(w,17,H,q){9 u;7(5.b.37[17]){u=2V("w.1t() "+5.b.37[17]+" H.1t()")}m{u=2V("w "+17+" H")}9 k=6.k(u,q);7(!k){7(/^(3I|==)$/.u(17)){6.3E(w,17,H)}m{6.4U(w,17,H)}}f k};5.b.l.4U=e(w,17,H){7(w!=z)w="\'"+w.1t()+"\'";7(H!=z)H="\'"+H.1t()+"\'";f 6.1c("    "+w+5.b.v+"        "+17+5.b.v+"    "+H)};5.b.l.3E=e(w,17,H){9 Q=[w,H];B(9 i=0;i<Q.p;i++){7(Q[i]!=z){Q[i]=17==\'3I\'?"\'"+Q[i].1t()+"\'":Q[i].4V()}}f 6.1c("         w: "+Q[0]+5.b.v+"    5R: "+Q[1]+5.b.v)};5.b.l.6K=e(V){6.1Q("6L x! "+V);F.36=e(){f G}1Y T 1E("6M")};5.b.l.1w=e(R){7(!6.1C)5.b.1n("1y 2I 1o 1G a u 2L a 1b! 3X 3Y a 1b.");7(R)R=R.1t().1z(5.b.2o,5.b.v+"# ");6.L++;6.E[6.L-1]={k:G,2P:G,q:\'\',Z:\'1w\',V:R};9 x="k";7(6.38)x+=\' \'+6.L;x+=" # 1w "+R+5.b.v;6.1Q(x);6.E[6.L-1].12=6.1A.3k(0).35(\'\');f G};5.b.l.2O=e(R){7(!6.1C)5.b.1n("1y 2I 1o 1G a u 2L a 1b! 3X 3Y a 1b.");7(R)R=R.1t().1z(5.b.2o,5.b.v+"# ");6.L++;6.E[6.L-1]={k:G,2P:K,q:\'\',Z:\'6N\',V:R};9 x="2f k";7(6.38)x+=\' \'+6.L;x+=" # 3F & 6P "+R+5.b.v;6.1Q(x);6.E[6.L-1].12=6.1A.3k(0).35(\'\');f G};5.b.l.3f=e(V){9 x="# 3T";7(V)x+=" "+V;x+=5.b.v;7(6.1N)6.1w(V);m{B(9 i=6.L;i<6.1a;i++){6.1w(V)}}F.36=e(){f G}1Y T 1E("6Q")};5.b.l.38=e(48){7(48!=z)6.4e=48;f 6.4e};5.b.l.28=e(28){7(28!=z)6.4G=!!28;f 6.4G};5.b.l.2K=e(2K){7(2K!=z)6.3L=!!2K;f 6.3L};5.b.l.1c=e(){7(!15.p)f;9 y=\'# \';B(i=0;i<15.p;i++){y+=15[i].1t().1z(5.b.2o,5.b.v+"# ")}7(!(T 3l(5.b.v+\'$\').u(y)))y+=5.b.v;f 6.4Z(y)};5.b.l.4Z=e(){9 A=6.S()?6.3i():6.3g();A.20(6,15);f K};5.b.l.12=e(A){7(A!=z){9 X=6.1A;6.58=e(y){X.10(y);A(y)}}f 6.58};5.b.l.3g=e(A){7(A!=z){9 X=6.1A;6.51=e(y){X.10(y);A(y)}}f 6.51};5.b.l.3i=e(A){7(A!=z){9 X=6.1A;6.56=e(y){X.10(y);A(y)}}f 6.56};5.b.l.3J=e(A){7(A!=z){9 X=6.1A;6.54=e(y){X.10(y);A(y)}}f 6.54};5.b.l.2R=e(A){7(A!=z){9 X=6.1A;6.53=e(y){X.10(y);A(y)}}f 6.53};5.b.l.39=e(){7(5.1q==\'2r\'){9 2S=e(y){9 D=C.3h("u");7(D){B(9 i=0;i<D.3M.p;i++){7(D.3M[i].6V==3){D.3M[i].6X(y);F.2p(0,C.1d.2v||C.1d.2l);f}}D.2n(C.3Q(y));F.2p(0,C.1d.2v||C.1d.2l);f}C.6Y(y);F.2p(0,C.1d.2v||C.1d.2l)};6.12(2S);6.3g(2S);6.3i(2S);6.3J(2S);7(F){7(F.3O.20)6.2R(F.3O,F);m 6.2R(e(y){F.3O(y)})}}m 7(5.1q==\'2z\'){6.12(3j);6.3g(3j);6.3i(3j);6.2R(3j)}f 6};5.b.l.72=e(25){7(25==z)f 6.L;7(!6.1C)5.b.1n("73\'t 74 31 75 u 2J 2L a 1b!");6.L=25;7(25>6.E.p){9 V=\'76 u 2J\';B(i=6.E.p;i<25;i++){6.E[i]={k:G,2P:z,V:V,Z:\'5u\',16:z,12:\'k - \'+V+5.b.v}}}m 7(25<6.E.p){6.E.3k(25,6.E.p)}f 6.L};5.b.l.3V=e(){9 3m=T 3B(6.E.p);B(9 i=0;i<6.E.p;i++){3m[i]=6.E[i][\'k\']}f 3m};5.b.l.77=e(){f 6.E};5.b.l.S=e(R,Y){7(Y)6.2e=[R,Y];f 6.2e[1]};5.b.l.57=e(){7(6.2e[1]){7(6.2e[1]--)f 6.2e[0];6.2e=[]}f K};5.b.l.5c=e(){5.b.3b(6.L<0,\'79 7a 1W 1k a 7b 2J 26 P!\');5.b.3b(!6.1C&&6.L,\'59 2s P 1k 2L a 1b!\');5.b.3b(6.L!=6.E.p,\'59 1W w a 7c 2J 26 3m 7d P 1k!\')};5.b.l.40=e(){7(I F!=\'N\'&&F.3n&&F.3n.5&&F.3n.5.o){F.3n.5.o.29++}};5.b.l.5t=e(){7(6.5a)f;6.5a=G;7(6.2K()){6.40();f}6.5c();9 x=6.3J();7(6.E.p){7(6.1N){7(!6.28())6.1Q("1.."+6.L+5.b.v);6.1a=6.L}9 2t=0;B(9 i=0;i<6.E.p;i++){7(!6.E[i])2t++}2t+=7g.7h(6.1a-6.E.p);7(6.L<6.1a){9 s=6.1a==1?\'\':\'s\';x("# 2u 1u 1W 5g "+6.1a+" u"+s+" 3W 7i 1k "+6.L+"."+5.b.v)}m 7(6.L>6.1a){9 5h=6.L-6.1a;9 s=6.1a==1?\'\':\'s\';x("# 2u 1u 1W 5g "+6.1a+" u"+s+" 3W 1k "+5h+" 7l."+5.b.v)}m 7(2t){9 s=2t==1?\'\':\'s\';x("# 2u 1u 1W 4z "+2t+"u"+s+" 26 "+6.1a+"."+5.b.v)}7(6.3p){x("# 2u 1u 2s u 5k 7n 7p "+6.L+"."+5.b.v)}}m 7(!6.3S){7(6.3p){x("# 2u 1u 2s u 5k 3U 1D 5o 12 7q."+5.b.v)}m{x("# 7r P 1G!"+5.b.v)}}6.40()};5.b.l.2H=e(w,17,H,q){9 u=2V("w "+(5.b.37[17]||17)+" H");6.k(u,q);7(!u)6.3E(w,17,H);f u};7(F){F.7s=e(){B(9 i=0;i<5.b.2w.p;i++){5.b.2w[i].44(0)}};F.36=e(y,5r,41){5.b.5.3p=G;5.b.5.1c("1E 1B "+5r+" 5U 41 "+41+": "+y);f G}};5.b.l.7u=e(43){9 1r=++6.46;7(43&&F&&F.30){9 5s=6;6.2F[1r]=F.30(e(){5s.44(1r)},43)}m{6.2F[1r]=0}f 1r};5.b.l.44=e(1r){7(6.2F[1r]==N)f;7(6.2F[1r]){F.7w(6.2F[1r])}7(--6.46<0)6.5t()};5.b.4R=e(2D,2C){7(I 2C==\'N\'){7(5.1q==\'2r\')2C=F;m 7(5.1q==\'2z\')2C=1P;m 1Y T 1E("7x 5u")}B(9 i=0;i<2D.2G.p;i++){7(I 2C[2D.2G[i]]==\'N\')2C[2D.2G[i]]=2D[2D.2G[i]]}};7(I 2E!=\'N\')T 2E().4F(\'5.b\');m{7(I 5==\'N\'||I 5.b==\'N\')1Y T 1E("1y 5x 7A 7B 2E 5N 5.b "+"3U 7C 5.h")}5.h={};5.h.2G=[\'1b\',\'k\',\'4c\',\'5A\',\'1u\',\'3s\',\'1T\',\'4f\',\'5F\',\'2A\',\'5I\',\'1c\',\'5J\',\'1w\',\'S\',\'2O\',\'3f\',\'4u\',\'61\',\'2Y\'];5.h.7E={\':3K\':5.h.2G};5.h.3A=\'0.11\';5.h.4n=G;5.b.1U={7I:\'5W 2f 5X\'};5.h.5=T 5.b();5.h.7J=e(){f 5.h.5};5.h.1b=e(3D){7(3D.5y){5.h.4n=K;5n 3D.5y}f 5.h.5.1b.20(5.h.5,[3D])};5.h.k=e(u,q){f 5.h.5.k(u,q)};5.h.4c=e(w,H,q){f 5.h.5.4y(w,H,q)};5.h.5A=e(w,H,q){f 5.h.5.5B(w,H,q)};5.h.1u=e(O,W,q){f 5.h.5.1u(O,W,q)};5.h.3s=e(O,W,q){f 5.h.5.3s(O,W,q)};5.h.1T=e(w,17,H,q){f 5.h.5.1T(w,17,H,q)};5.h.4f=e(1S){9 k;7(!15.p>1){k=5.h.5.k(K,1e+\'.4h(...)\');5.h.5.1c(\'    4f() 7N 7Q 42 7S\');f k}9 1e;7(I 1S==\'2T\'){1e=1S;1S=2V(1e+\'.l\')}m{1e=5.b.2q(1S);1S=1S.4l.l}9 2X=[];B(9 i=1;i<15.p;i++){9 4g=15[i];7(I 1S[4g]!=\'e\')2X.10(4g)}9 q=1e+".4h(\'"+(15.p==2?15[1]:\'...\')+"\')";k=5.h.5.k(!2X.p,q);B(9 i=0;i<2X.p;i++){5.h.5.1c(\'    \'+1e+".4h(\'"+2X[i]+"\') 4z")}f k};5.h.5F=e(U,1e,22){9 2d;7(22==z)22=\'7W U\';9 16=22+\' 2Y \'+1e;7(U==z){2d=22+" 3u\'t 7Y"}m 7(!5.h.21(U)){2d=22+" 3u\'t a 7Z"}m{9 4m=2V(1e);7(2m.5H){7(!4m.l.5H(U)){2d=22+" 3u\'t a \'"+1e+"\' 1D\'s a \'"+5.b.2q(U)+"\'"}}m{7(U.4l!=4m)2d=22+" 3u\'t a \'"+1e+"\' 1D\'s a \'"+5.b.2q(U)+\'"\'}}9 k;7(2d){k=5.h.5.k(K,16);5.h.5.1c(\'    \'+2d)}m{k=5.h.5.k(G,16)}f k};5.h.2A=e(16){f 5.h.5.k(G,16)};5.h.5I=e(16){f 5.h.5.k(K,16)};5.h.1c=e(){7(!5.h.4n)f;f 5.h.5.1c.20(5.h.5,15)};5.h.5J=e(){f K};5.h.1w=e(R,Y){7(Y==z){7(!5.b.1N)5.h.5.32("1w() 4o 1o 4p Y P 4q 1B 31 4r");Y=1}B(i=0;i<Y;i++){5.h.5.1w(R)}};5.h.S=e(R,Y){7(Y==z){7(!5.b.1N)5.h.5.32("S() 4o 1o 4p Y P 4q 1B 31 4r");Y=1}f 5.h.5.S(R,Y)};5.h.2O=e(R,Y){7(Y==z){7(!5.b.1N)5.h.5.32("2O() 4o 1o 4p Y P 4q 1B 31 4r");Y=1}B(i=0;i<Y;i++){5.h.5.2O(R)}};5.h.3f=e(R){5.h.5.3f(R)};5.h.4u=e(1D,33,16){7(15.p!=2&&15.p!=3){5.h.5.32(\'4u() 86 87 5N 88 Q, 1W 5P \'+15.p+".")}9 k;7(5.h.21(1D)^5.h.21(33)){k=K}m 7(!5.h.21(1D)&&!5.h.21(33)){k=5.h.5.4y(1D,33,16)}m{9 J=[],M=[];7(5.h.3o(1D,33,J,M)){k=5.h.5.k(G,16)}m{k=5.h.5.k(K,16);5.h.5.1c(5.h.4N(J))}}f k};5.h.3o=e(18,19,J,M){9 k=K;9 5Q=!(!5.h.21(18)^!5.h.21(19));7(18==z&&19==z){k=G}m 7(18!=z^19!=z){k=K}m 7(18==5.h.1U^19==5.h.1U){k=K}m 7(5Q&&18==19){k=G}m 7(2Y(18,\'3B\')&&2Y(19,\'3B\')){k=5.h.4M(18,19,J,M)}m 7(I 18=="U"&&I 19=="U"){k=5.h.4I(18,19,J,M)}m{J.10({1H:[18,19]});k=K}f k};5.h.21=e(U){9 Z=I U;f Z==\'U\'||Z==\'e\'};5.h.4N=e(J){9 34=\'$4D\';B(9 i=0;i<J.p;i++){9 4B=J[i];9 Z=4B[\'Z\'];9 1v=4B[\'1v\'];7(1v!=z){7(/^\\d+$/.u(1v)){34+=\'[\'+1v+\']\'}m{1v=1v.1z("\'","\\\\\'");34+="[\'"+1v+"\']"}}}9 1H=J[J.p-1][\'1H\'].3z(0,2);9 4H=[34.1z(\'$4D\',\'w\'),34.1z(\'$4D\',\'5R\')];9 x="89 8b 8d 5U:"+5.b.v;B(9 i=0;i<1H.p;i++){9 O=1H[i];7(O==z){O=\'N\'}m{O==5.h.1U?"5W 2f 5X":"\'"+O+"\'"}}x+=4H[0]+\' = \'+1H[0]+5.b.v;x+=4H[1]+\' = \'+1H[1]+5.b.v;f\'    \'+x};5.h.4M=e(1l,1g,J,M){7(1l==1g)f G;B(9 j=0;j<M.p;j++){7(M[j][0]==1l){f M[j][1]==1g}}M.10([1l,1g]);9 k=G;9 1J=1l.p>1g.p?1l.p:1g.p;7(1J==0)f 5.h.4I(1l,1g,J,M);B(9 i=0;i<1J;i++){9 18=i>1l.p-1?5.h.1U:1l[i];9 19=i>1g.p-1?5.h.1U:1g[i];J.10({Z:\'3B\',1v:i,1H:[18,19]});7(k=5.h.3o(18,19,J,M)){J.4O()}m{5V}}f k};5.h.4I=e(1L,23,J,M){7(1L==23)f G;M=M.3z(0);B(9 j=0;j<M.p;j++){7(M[j][0]==1L){f M[j][1]==23}}M.10([1L,23]);9 k=G;9 4J=0;B(9 i 1B 1L)4J++;9 4K=0;B(9 i 1B 23)4K++;9 5G=4J>4K?1L:23;B(9 i 1B 5G){9 18=1L[i]==N?5.h.1U:1L[i];9 19=23[i]==N?5.h.1U:23[i];J.10({Z:\'2m\',1v:i,1H:[18,19]});7(k=5.h.3o(18,19,J,M)){J.4O()}m{5V}}f k};5.h.5O=e(1l,1g,J,M){f 5.h.4M(1l.3z(0).5T(),1g.3z(0).5T(),J,M)};5.h.61=e(1l,1g,q){9 J=[],M=[],k=G;7(5.h.5O(1l,1g,J,M)){k=5.h.5.k(G,q)}m{k=5.h.5.k(K,q);5.h.5.1c(5.h.4N(J))}f k};5.h.2Y=e(U,1e){f 5.b.2q(U)==1e};7(I 2E==\'N\')5.b.4R(5.h);7(I 4T!=\'N\'){7(I 5==\'N\')5={1q:\'2r\'};m 5.1q=\'2r\'}m 7(I 6G!=\'N\'){7(I 1P.5!="U")1P.5={1q:\'2z\'};m 1P.5.1q=\'2z\'}m{1Y T 1E("5.o 4W 2f 4Q 2s 4X")}5.o=e(){};5.o.3A=\'0.11\';5.o.29=0;5.o.v=I C!="N"&&I C.3K!="N"?"\\r":"\\n";5.o.l.50=5.o.50;5.o.l.2Q=0;5.o.l.1k=0;5.o.l.k=0;5.o.l.1X=0;5.o.l.S=0;5.o.l.13=0;5.o.l.3C=0;5.o.l.2B=0;5.o.l.P=0;5.o.l.1K=0;5.o.l.2j=[];5.o.2U=e(){9 1h=T 5.o();1h.2U.20(1h,15)};5.o.l.5b=e(13){9 1R=0;B(9 i=0;i<13.p;i++){7(13[i].p>1R)1R=13[i].p}1R+=3;9 2N=[];B(9 i=0;i<13.p;i++){9 3P=13[i];9 52=1R-13[i].p;B(9 j=0;j<52;j++){3P+=\'.\'}2N.10(3P)}f 2N};5.o.l.5j=e(u,1O,A,55){6.P++;6.1k+=u.E.p;9 1x=[];9 1f={A:1O,3R:u.3N,k:0,2c:[]};7(u.E.p){6.13++;9 2A=G;B(9 i=0;i<u.E.p;i++){9 k="k";7(u.E[i].k){6.k++;1f.k++7(u.E[i].Z==\'S\'){7(u.E[i].6Z)6.2Q++;6.S++}m 7(u.E[i].Z==\'1w\')6.1X++}m{7(u.E[i].Z==\'S\'){6.S++}m{2A=K;1f.2c.10(i+1)}k="2f k"}7(!2A||55.4x)A(u.E[i].12)}7(2A){6.3C++;A("k"+5.o.v)}m{6.2B++;9 3c="78 # 1p ";7(1f.2c.p==1){3c+="u "+1f.2c[0]}m{3c+="P "+6.47(1f.2c)}A(3c+" 1B "+1O+5.o.v)}}m 7(u.3S){6.1K++;6.3C++;A("1..0 # 3T 1"+5.o.v)}m{6.13++;6.2B++;A("3Z 3U 7e u 12 7f"+5.o.v)}7(1f.2c.p)6.2j.10(1f)};5.o.l.5e=e(){f 6.2B==0&&(6.1k||6.1K)?G:K};5.o.l.5l=e(A,5E){9 1j=6.5K();9 4a;7(6.5e()){A("7j P 7k"+1j+\'.\'+5.o.v)}m 7(!6.P){A("3Z—42 P 7m 1G B 7o V."+5.o.v)}m 7(!6.1k){9 5m=6.P==1?"1O":"13";A("3Z—"+6.P+" u "+5m+" 5o 5p 1G, "+"7t—42 12 7v M."+5.o.v)}m{4a=6.3C/6.P*4s;9 5v=4s*6.k/6.1k;9 5D=(6.1k-6.k)+"/"+6.1k+" 7y 4z, "+5v.5z(4)+"% 5C.";7(6.2B){1j=1j.1z(/^,?\\s*/,\'\');7(1j)A(1j+\'.\'+5.o.v);A("1p "+6.2B+"/"+6.P+" u 7G, "+4a.5z(4)+"% 5C. "+5D+5.o.v)}6.4b(A)}A("7M="+6.P+", 7O="+6.1k+", "+(5E/7U)+" 7X"+5.o.v)};5.o.l.4b=e(){9 27=\'\';9 2x="1p 5";9 2y=" 4C 4E  1p  ";9 3v="5L 26 1p";9 3t=80;9 1F=2x.p;B(9 i=0;i<6.2j.p;i++){9 1R=6.2j[i].p;7(1R>1F)1F=1R}9 2Z=3t-2y.p-1F.p;7(2Z<3v.p){2Z=3v.p;1F=3t-2y.p-2Z;7(1F<2x.p){1F=2x.p;3t=1F+2y.p+2Z}}9 x=2x;7(x.p<1F){B(9 j=x.p;j<81;j++){x+=\' \'}}x+=\'  \'+2y};5.o.l.5K=e(){9 1j=\'\';7(6.2Q){1j=(" ("+6.2Q+" 4v"+(6.2Q>1?\'s\':\'\')+" 83 84)")}7(6.1K){1j+=", "+6.1K+" u"+(6.1K!=1?\'s\':\'\');7(6.1X){1j+=" 85 "+6.1X+" 4v"+(6.1X!=1?\'s\':\'\')}1j+=\' 1K\'}m 7(6.1X){1j+=", "+6.1X+" 4v"+(6.1X!=1?\'s\':\'\')+" 1K"}f 1j}5.o.l.47=e(1x){9 1I=-1;9 2g=\'\';9 1Z=[];B(9 i=0;i<1x.p;i++){7(2g){7(1x[i]-1==1I){1I=1x[i]}m{1Z[1Z.p-1]+=2g+1I;1I=-1;1Z.10(1x[i]);2g=\'\'}}m 7(1x[i]-1==1I){1I=1x[i];2g=\'-\'}m{1Z.10(1x[i]);1I=1x[i]}}7(2g)1Z[1Z.p-1]+=2g+1I;f 1Z.35(\' \')}7(I 2E!=\'N\')T 2E().4F(\'5.o\');5.o.14=e(){};5.o.14.3A=\'0.11\';5.o.14.2U=e(){9 1h=T 5.o.14();1h.2U.20(1h,15)};5.o.14.l=T 5.o();5.o.14.l.3x=4s;5.o.14.l.5d=e(){9 D=C.3h(\'X\');7(D)f D.4S;D=C.49("8o");D.2M("1r","X");D.2M("16","X");7(/67/.u(6d.6h)){D.2k.6l="6n";D.2k.6r="0";D.2k.6v="0"}m D.2k.6y="6C";C.1d.2n(D);f D.4S};5.o.14.l.39=e(){9 D=C.49("4Y");D.2M("1r","12");C.1d.2n(D);f e(y){D.2n(C.3Q(y));F.2p(0,C.1d.2v||C.1d.2l)}};5.o.14.l.5i=e(){9 D=C.49("6I");D.2M("1r","3V");D.2M("2k","6O-6R:4Y; 6S-6U: 6W,70,71;");C.1d.2n(D);f e(y){D.2n(C.3Q(y));F.2p(0,C.1d.2v||C.1d.2l)}};5.o.14.l.2U=e(){9 13=6.Q.1O?I 6.Q.1O==\'2T\'?[6.Q.1O]:6.Q.1O:15;7(!13.p)f;9 4w=6.5b(13);9 X=6.5d();9 1h=6;9 2h=0;9 4A;9 D=C.3h(\'12\');9 12=6.39();9 5q=6.5i();9 45=e(){},4k=e(){};9 4j=e(){1h.5j(X.5.b.5,13[2h],12,1h.Q);7(13[++2h]){12(4w[2h]+(1h.Q.4x?5.o.v:\'\'));X.4i.5Y=13[2h];4k()}m{1h.5l(5q,T 5S()-4A);45()}};7(2m.5w){45=e(){5.o.7z(\'29\')};5.o.5w(\'29\',e(7D,7K,4d){7(4d<X.5.b.2w.p)f 4d;4j();f 0})}m{9 3w=e(){7(5.o.29>0&&5.o.29>=X.5.b.2w.p){5.o.29=0;4j()}m{F.30(3w,1h.3x)}};4k=e(){F.30(3w,1h.3x)};F.30(3w,6.3x)}12(4w[2h]+(6.Q.4x?5.o.v:\'\'));4A=T 5S();X.4i.5Y=13[2h]};5.o.14.l.Q={};9 2i=4i.6e.3q(1).5M(",");B(9 i=0;i<2i.p;i++){9 3d=2i[i].6s(\'=\');7(3d==-1)6z;9 2a=2i[i].3q(0,3d);9 O=2i[i].3q(3d+1);7(5.o.14.l.Q[2a]){7(I 5.o.14.l.Q[2a]==\'2T\'){5.o.14.l.Q[2a]=[5.o.14.l.Q[2a]]}5.o.14.l.Q[2a].10(5f(O))}m{5.o.14.l.Q[2a]=5f(O)}}5n 2i;5.o.14.l.4b=e(A){9 2x="1p 5";9 2y=" 4C 4E  1p  ";9 3v="5L 26 1p";9 27=\'<27 2k=""><3y><24>1p 5</24><24>4C</24>\'+\'<24>4E</24><24>1p</24></3y>\';B(9 i=0;i<6.2j.p;i++){9 1f=6.2j[i];27+=\'<3y><1V>\'+1f.A+\'</1V>\'+\'<1V>\'+1f.3R+\'</1V>\'+\'<1V>\'+1f.3R-1f.k+\'</1V>\'+\'<1V>\'+6.47(1f.2c)+\'</1V></3y>\'};27+=\'</27>\'+5.o.v;9 D=C.3h(\'3V\');D.7L+=27;F.2p(0,C.1d.2v||C.1d.2l)};',62,521,'|||||Test|this|if||var||Builder|||function|return||More|||ok|prototype|else||Harness|length|desc||||test|LF|got|out|msg|null|fn|for|document|node|TestResults|window|true|expect|typeof|stack|false|CurrTest|seen|undefined|val|tests|args|why|todo|new|object|reason|regex|buffer|howMany|type|push||output|files|Browser|arguments|name|op|e1|e2|ExpectedTests|plan|diag|body|clas|track|a2|harness|result|bonusmsg|ran|a1|arg|die|to|Failed|PLATFORM|id|cmd|toString|like|idx|skip|fails|You|replace|Buffer|in|HavePlan|it|Error|maxNamelen|run|vals|last|max|skipped|o1|dontExpect|NoPlan|file|_global|_print|len|proto|cmpOK|DNE|td|you|subSkipped|throw|list|apply|_isRef|objName|o2|th|num|of|table|noHeader|Done|key|cmp|failList|mesg|ToDo|not|dash|ti|pairs|failures|style|scrollHeight|Object|appendChild|lineEndingRx|scrollTo|typeOf|browser|your|numFailed|Looks|offsetHeight|Instances|failedStr|middleStr|director|pass|bad|root|pkg|JSAN|asyncs|EXPORT|isUndef|tried|number|noEnding|without|setAttribute|ret|todoSkip|actual_ok|bonus|warnOutput|writer|string|runTests|eval|Number|nok|isa|listLen|setTimeout|the|warn|as|variable|join|onerror|StringOps|useNumbers|_setupOutput|noPlan|_whoa|err|pos|match|skipRest|failureOutput|getElementById|todoOutput|trace|splice|RegExp|results|parent|_deepCheck|TestDied|substring|doesn|unlike|cols|isn|listStr|wait|interval|tr|slice|VERSION|Array|good|cmds|_isDiag|TODO|_regexOK|_diagLike|eq|endOutput|all|NoEnding|childNodes|expectedTests|alert|outName|createTextNode|total|SkipAll|Skip|before|summary|but|Gotta|have|FAILED|_notifyHarness|line|no|timeout|endAsync|finish|asyncID|_failList|useNums|createElement|pct|formatFailures|is|next|UseNums|canOK|method|can|location|runner|runNext|constructor|ctor|ShowDiag|needs|know|are|block|100|skipAll|isDeeply|subtest|outfiles|verbose|isEq|failed|start|entry|Total|Foo|Fail|use|NoHeader|vars|_eqAssoc|o1Size|o2Size|reset|_eqArray|_formatStack|pop|understand|support|exporter|contentWindow|self|_cmpDiag|valueOf|does|platform|pre|_printDiag|isDone|FailureOutput|add|WarnOutput|EndOutput|attrs|TodoOutput|_todo|Output|Somehow|Ended|outFileNames|_sanity_check|_setupFrame|_allOK|unescape|planned|numExtra|_setupSummary|outputResults|died|outputSummary|blurb|delete|could|be|summaryOutput|url|aTest|_ending|unknown|pctOK|watch|must|noDiag|toPrecision|isnt|isntEq|okay|subpct|time|isaOK|bigger|isPrototypeOf|fail|loadOK|_bonusmsg|List|split|or|_eqSet|gave|sameRef|expected|Date|sort|at|break|Does|exist|href|call|check|isSet|Please|contact|author|immediately|Function|Safari|create|twice|TestBulder|Got|an|navigator|search|how|made|userAgent|said|ve|something|visibility|isNaN|hidden|postive|integer|hasPlan|height|indexOf|__SKIP_ALL__|Your|width|shouldn|names|display|continue|confusing|isNum|none|isntNum|look|me|_player|matches|div|source|BAILOUT|Bail|__BAILOUT__|todo_skip|white|SKIP|__SKIP_REST__|space|font|happen|family|nodeType|Verdana|appendData|write|actualOK|Arial|serif|currentTest|Can|change|current|incrementing|details|NOK|Says|here|negative|different|than|any|arrived|Math|abs|only|All|successful|extra|were|just|some|after|anything|No|onload|alas|beginAsync|ever|clearTimeout|Platform|subtests|unwatch|load|either|loading|attr|EXPORT_TAGS|much|scripts|toLowerCase|dne|builder|prev|innerHTML|Files|called|Tests|Very|with|numbers|methods|description|1000|startsNumber|The|seconds|defined|reference||maxNameLength|gt|UNEXPECTEDLY|SUCCEEDED|and|takes|two|three|Structures|mistake|begin|many|differing|say|ne|lt|ge|instanceof|le|WHOA|This|should|never|iframe'.split('|'),0,{}))
+
diff --git a/test/lib/test.js b/test/lib/test.js
new file mode 100644 (file)
index 0000000..7465817
--- /dev/null
@@ -0,0 +1,32 @@
+function q() {
+       var r = new Array();
+       for ( var i = 0; i < arguments.length; i++ ) {
+               r.push( document.getElementById( arguments[i] ) );
+       }
+       return r;
+}
+
+function t(a,b,c) {
+       var f = jQuery.find(b);
+       var s = "";
+       for ( var i = 0; i < f.length; i++ )
+               s += (s?",":"") + '"' + f[i].id + '"';
+       isSet(f,q.apply(q,c),a + " (" + b + ")") ||
+               diag( s );
+}
+
+function o(a) {
+       var li = document.createElement("li");
+       li.innerHTML = a;
+       if ( a.indexOf("#") == 0 )
+               li.className = "comment";
+       else if ( a.indexOf("TODO") >= 0 )
+               li.className = "todo";
+       else if ( a.indexOf("not ok") == 0 )
+               li.classname = "fail";
+       else
+               li.className = "pass";
+       document.getElementById("test").appendChild(li);
+}
+
+plan({noPlan: true});
diff --git a/test/tests/basic.html b/test/tests/basic.html
new file mode 100644 (file)
index 0000000..9710083
--- /dev/null
@@ -0,0 +1,86 @@
+<html id="html">
+<head>
+       <script type="text/javascript" src="../lib/Test/Builder.js"></script>
+       <script type="text/javascript" src="../lib/Test/More.js"></script>
+       <script type="text/javascript" src="../lib/test.js"></script>
+       <script type="text/javascript" src="../../jquery/jquery-svn.js"></script>
+</head>
+<body id="body">
+       <h1>Basic Usage</h1>
+
+       <!-- Test HTML -->
+       <div id="main" style="display: none;"></div>
+       <div id="foo" style="display: none;"></div>
+
+       <pre id="test"><script>
+               plan({tests: 11});
+               var div = $("div");
+
+               diag( "Getters" );
+               
+               cmpOK( div.size(), "==", 2, "Get Number of Elements Found" );
+               
+               isSet( div.get(), q("main","foo"), "Get All Elements" );
+               cmpOK( div.get(0), "==", document.getElementById("main"), 
+                       "Get A Single Element" );
+                       
+               diag( "Setters" );
+                       
+               div.each(function(){this.foo = 'zoo';});
+               var pass = true;
+               for ( var i = 0; i < div.size(); i++ ) {
+                       if ( div.get(i).foo != "zoo" ) pass = false;
+               }
+               ok( pass, "Execute, Relative" );
+               
+               div.addClass("test");
+               var pass = true;
+               for ( var i = 0; i < div.size(); i++ ) {
+                       if ( div.get(i).className.indexOf("test") == -1 ) pass = false;
+               }
+               ok( pass, "Add Class" );
+               
+               div.removeClass("test");
+               var pass = true;
+               for ( var i = 0; i < div.size(); i++ ) {
+                       if ( div.get(i).className.indexOf("test") != -1 ) pass = false;
+               }
+               ok( pass, "Remove Class" );
+               
+               div.html("<b>test</b>");
+               var pass = true;
+               for ( var i = 0; i < div.size(); i++ ) {
+                       if ( div.get(i).childNodes.length == 0 ) pass = false;
+               }
+               ok( pass, "Set HTML" );
+               
+               div.attr("foo", "bar");
+               var pass = true;
+               for ( var i = 0; i < div.size(); i++ ) {
+                       if ( div.get(i).foo != "bar" ) pass = false;
+               }
+               ok( pass, "Set Attribute" );
+               
+               div.attr({foo: 'baz', zoo: 'ping'});
+               var pass = true;
+               for ( var i = 0; i < div.size(); i++ ) {
+                       if ( div.get(i).foo != "baz" && div.get(i).zoo != "ping" ) pass = false;
+               }
+               ok( pass, "Set Multiple Attributes" );
+
+               diag( "Effects" );
+               
+               var pass = true;
+               div.show().each(function(){
+                       if ( this.style.display == "none" ) pass = false;
+               });
+               ok( pass, "Show" );
+               
+               var pass = true;
+               div.hide().each(function(){
+                       if ( this.style.display != "none" ) pass = false;
+               });
+               ok( pass, "Hide" );
+       </script></pre>
+</body>
+</html>
diff --git a/test/tests/css1.html b/test/tests/css1.html
new file mode 100644 (file)
index 0000000..014fe4f
--- /dev/null
@@ -0,0 +1,57 @@
+<html id="html">
+<head>
+       <script type="text/javascript" src="../lib/Test/Builder.js"></script>
+       <script type="text/javascript" src="../lib/Test/More.js"></script>
+       <script type="text/javascript" src="../lib/test.js"></script>
+       <script type="text/javascript" src="../../jquery/jquery-svn.js"></script>
+</head>
+<body id="body">
+       <h1>CSS 1 Selectors</h1>
+
+       <!-- Test HTML -->
+       <div id="main" style="display: none;">
+               <p id="first">See <a id="simon1" href="http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector" rel="bookmark">this blog entry</a> 
+               for more information.</p><p>Here are some links in a normal paragraph: 
+               <a id="google" href="http://www.google.com/" title="Google!">Google</a>, 
+               <a id="groups" href="http://groups.google.com/">Google Groups</a>. 
+               This link has <code><a href="#" id="anchor1">class="blog"</a></code>: 
+               <a href="http://diveintomark.org/" class="blog" hreflang="en" id="mark">diveintomark</a></p>
+               <div id="foo"><p>Everything inside the red border is inside a div with 
+               <code>id="foo"</code>.</p><p lang="en">This is a normal link: 
+               <a id="yahoo"href="http://www.yahoo.com/">Yahoo</a></p>
+               <p>This link has <code><a href="#2" id="anchor2">class="blog"</a></code>: 
+               <a href="http://simon.incutio.com/" class="blog link" id="simon">Simon Willison's Weblog</a></p></div>
+               <p>Try them out: </p>
+               <ul id="first"></ul>
+       </div>
+
+       <pre id="test"><script>
+               plan({tests: 16});
+
+               t( "Element Selector", "div", ["main","foo"] );
+               t( "Element Selector", "body", ["body"] );
+               t( "Element Selector", "html", ["html"] );
+               cmpOK( $("*").size(), ">=", 30, "Element Selector" );
+               t( "Parent Element", "div div", ["foo"] );
+               
+               t( "ID Selector", "#body", ["body"] );
+               t( "ID Selector w/ Element", "body#body", ["body"] );
+               cmpOK($("ul#first").get(0), "==", document.getElementsByTagName("ul")[0],
+                       "ID Selector w/ Element");
+       
+               t( "Class Selector", ".blog", ["simon","mark"] );
+               t( "Class Selector", ".blog.link", ["simon"] );
+               t( "Class Selector w/ Element", "a.blog", ["simon","mark"] );
+               t( "Parent Class Selector", "p .blog", ["simon","mark"] );
+               
+               t( "Comma Support", "a.blog, div", 
+                       ["main","foo","simon","mark"] );
+               t( "Comma Support", "a.blog , div", 
+                       ["main","foo","simon","mark"] );
+               t( "Comma Support", "a.blog ,div", 
+                       ["main","foo","simon","mark"] );
+               t( "Comma Support", "a.blog,div", 
+                       ["main","foo","simon","mark"] );
+       </script></pre>
+</body>
+</html>
diff --git a/test/tests/css2.html b/test/tests/css2.html
new file mode 100644 (file)
index 0000000..5f19308
--- /dev/null
@@ -0,0 +1,66 @@
+<html id="html">
+<head>
+       <script type="text/javascript" src="../lib/Test/Builder.js"></script>
+       <script type="text/javascript" src="../lib/Test/More.js"></script>
+       <script type="text/javascript" src="../lib/test.js"></script>
+       <script type="text/javascript" src="../../jquery/jquery-svn.js"></script>
+</head>
+<body id="body">
+       <h1>CSS 2 Selectors</h1>
+
+       <!-- Test HTML -->
+       <div id="main" style="display: none;">
+               <p id="firstp">See <a id="simon1" href="http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector" rel="bookmark">this blog entry</a> 
+               for more information.</p>
+               <p id="ap">Here are some links in a normal paragraph: 
+               <a id="google" href="http://www.google.com/" title="Google!">Google</a>, 
+               <a id="groups" href="http://groups.google.com/">Google Groups</a>. 
+               This link has <code><a href="#" id="anchor1">class="blog"</a></code>: 
+               <a href="http://diveintomark.org/" class="blog" hreflang="en" id="mark">diveintomark</a></p>
+               <div id="foo"><p id="sndp">Everything inside the red border is inside a div with 
+               <code>id="foo"</code>.</p><p lang="en" id="en">This is a normal link: 
+               <a id="yahoo" href="http://www.yahoo.com/" class="blogTest">Yahoo</a></p>
+               <p id="sap">This link has <code><a href="#2" id="anchor2">class="blog"</a></code>: 
+               <a href="http://simon.incutio.com/" class="blog link" id="simon" rel="bookmark link">Simon Willison's Weblog</a></p></div>
+               <p id="first">Try them out: </p>
+               <ul id="first"></ul>
+       </div>
+       <pre id="test"><script>
+               plan({tests: 20});
+                       
+               t( "Child", "p > a",
+                       ["simon1","google","groups","mark","yahoo","simon"] );
+               t( "Child", "p> a",
+                       ["simon1","google","groups","mark","yahoo","simon"] );
+               t( "Child", "p >a",
+                       ["simon1","google","groups","mark","yahoo","simon"] );
+               t( "Child", "p>a",
+                       ["simon1","google","groups","mark","yahoo","simon"] );
+               t( "Child w/ Class", "p > a.blog",
+                       ["mark","simon"] );
+               t( "All Children", "code > *",
+                       ["anchor1","anchor2"] );
+               t( "All Grandchildren", "p > * > *",
+                       ["anchor1","anchor2"] );
+                       
+               t( "Adjacent", "a + a", ["groups"] );
+               t( "Adjacent", "a +a", ["groups"] );
+               t( "Adjacent", "a+ a", ["groups"] );
+               t( "Adjacent", "a+a", ["groups"] );
+               t( "Adjacent", "p + p", ["ap","en","sap"] );
+
+               t( "Comma, Child, and Adjacent", "a + a, code > a", 
+                       ["anchor1","anchor2","groups"] );
+                       
+               t( "First Child", "p:first-child", ["firstp","sndp"] );
+               
+               diag( "Note: @ is used for XPath compatability" );
+               t( "Attribute Exists", "a[@title]", ["google"] );
+               t( "Attribute Exists", "*[@title]", ["google"] );
+               t( "Attribute Exists", "[@title]", ["google"] );
+               t( "Attribute Equals", "a[@rel='bookmark']", ["simon1"] );
+               t( "Attribute Equals", 'a[@rel="bookmark"]', ["simon1"] );
+               t( "Attribute Equals", "a[@rel=bookmark]", ["simon1"] );
+       </script></pre>
+</body>
+</html>
diff --git a/test/tests/css3.html b/test/tests/css3.html
new file mode 100644 (file)
index 0000000..ced1f9c
--- /dev/null
@@ -0,0 +1,79 @@
+<html id="html">
+<head>
+       <script type="text/javascript" src="../lib/Test/Builder.js"></script>
+       <script type="text/javascript" src="../lib/Test/More.js"></script>
+       <script type="text/javascript" src="../lib/test.js"></script>
+       <script type="text/javascript" src="../../jquery/jquery-svn.js"></script>
+</head>
+<body id="body">
+       <h1>CSS 3 Selectors</h1>
+
+       <!-- Test HTML -->
+       <div id="main" style="display: none;">
+               <p id="firstp">See <a id="simon1" href="http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector" rel="bookmark">this blog entry</a> for more information.</p>
+               <p id="ap">
+                       Here are some links in a normal paragraph: <a id="google" href="http://www.google.com/" title="Google!">Google</a>, 
+                       <a id="groups" href="http://groups.google.com/">Google Groups</a>. 
+                       This link has <code><a href="#" id="anchor1">class="blog"</a></code>: 
+                       <a href="http://diveintomark.org/" class="blog" hreflang="en" id="mark">diveintomark</a>
+               </p>
+               <div id="foo">
+                       <p id="sndp">Everything inside the red border is inside a div with 
+                               <code>id="foo"</code>.</p>
+                       <p lang="en" id="en">This is a normal link: 
+                               <a id="yahoo" href="http://www.yahoo.com/" class="blogTest">Yahoo</a></p>
+                       <p id="sap">This link has <code><a href="#2" id="anchor2">class="blog"</a></code>: 
+                               <a href="http://simon.incutio.com/" class="blog link" id="simon">Simon Willison's Weblog</a></p>
+               </div>
+               <p id="first">Try them out: </p>
+               <ul id="first"></ul>
+               <ol id="empty"></ol>
+               <form>
+                       <input type="text" value="Test" id="text1"/>
+                       <input type="text" value="Test" id="text2" disabled="disabled"/>
+                       <input type="radio" name="radio1" id="radio1"/>
+                       <input type="radio" name="radio2" id="radio2" checked/>
+                       <input type="checkbox" name="check" id="check1" checked/>
+                       <input type="checkbox" name="check" id="check2"/>
+                       </form>
+       </div>
+
+       <pre id="test"><script>
+               plan({tests: 14});
+                       
+               t( "Attribute Begins With", "a[@href ^= 'http://www']",
+                       ["google","yahoo"] );
+               t( "Attribute Ends With", "a[@href $= 'org/']",
+                       ["mark"] );
+               t( "Attribute Contains", "a[@href *= 'google']",
+                       ["google","groups"] );
+        
+               t( "First Child", "p:first-child",
+                       ["firstp","sndp"] );
+               t( "Last Child", "p:last-child",
+                       ["sap"] );
+        
+               t( "Only Child", "a:only-child",
+                       ["simon1","anchor1","anchor2","yahoo"] );
+        
+               t( "Empty", "ol:empty", ["empty"] );
+        
+               t( "Enabled UI Element", "input:enabled",
+                       ["text1","radio1","radio2","check1","check2"] );
+               t( "Disabled UI Element", "input:disabled",
+                       ["text2"] );
+      
+               t( "Checked UI Element", "input:checked",
+                       ["radio2","check1"] );
+        
+               t( "Text Contains", "a:contains('Google')",
+                       ["google","groups"] );
+               t( "Text Contains", "a:contains('Google Groups')",
+                       ["groups"] );
+        
+               t( "Element Preceded By", "p ~ div", ["foo"] );
+       
+               t( "Not", "a.blog:not(.link)", ["mark"] );
+       </script></pre>
+</body>
+</html>
diff --git a/test/tests/custom.html b/test/tests/custom.html
new file mode 100644 (file)
index 0000000..8341a2f
--- /dev/null
@@ -0,0 +1,66 @@
+<html id="html">
+<head>
+       <script type="text/javascript" src="../lib/Test/Builder.js"></script>
+       <script type="text/javascript" src="../lib/Test/More.js"></script>
+       <script type="text/javascript" src="../lib/test.js"></script>
+       <script type="text/javascript" src="../../jquery/jquery-svn.js"></script>
+</head>
+<body id="body">
+       <h1>Custom Expressions</h1>
+
+       <!-- Test HTML -->
+       <div id="main" style="display: none;">
+               <p id="firstp">See <a id="simon1" href="http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector" rel="bookmark">this blog entry</a> 
+                       for more information.</p>
+               <p id="ap">Here are some links in a normal paragraph: 
+                       <a id="google" href="http://www.google.com/" title="Google!">Google</a>, 
+                       <a id="groups" href="http://groups.google.com/">Google Groups</a>. 
+                       This link has <code><a href="#" id="anchor1">class="blog"</a></code>: 
+                       <a href="http://diveintomark.org/" class="blog" hreflang="en" id="mark">diveintomark</a>
+               </p>
+               <div id="foo">
+                       <p id="sndp">Everything inside the red border is inside a div with 
+                               <code>id="foo"</code>.</p>
+                       <p lang="en" id="en">This is a normal link: 
+                               <a id="yahoo" href="http://www.yahoo.com/" class="blogTest">Yahoo</a></p>
+                       <p id="sap">This link has <code><a href="#2" id="anchor2">class="blog"</a></code>: 
+                               <a href="http://simon.incutio.com/" class="blog link" id="simon">Simon Willison's Weblog</a></p>
+               </div>
+               <p id="first">Try them out: </p>
+               <ol id="empty"></ol>
+               <form id="form">
+                       <input type="hidden" value="Test" id="hidden"/>
+                       <input type="text" value="Test" id="text1" style="display:none;"/>
+                       <input type="text" value="Test" id="text2" disabled="disabled"/>
+                       <input type="radio" name="radio1" id="radio1"/>
+                       <input type="radio" name="radio2" id="radio2" checked/>
+                       <input type="checkbox" name="check" id="check1" checked/>
+                       <input type="checkbox" name="check" id="check2" style="visibility:hidden;"/>
+               </form>
+       </div>
+       
+       <pre id="test"><script>
+               plan({tests: 11});
+               
+               t( "nth Element", "p:nth(1)", ["ap"] );
+               t( "First Element", "p:first", ["firstp"] );
+               t( "Last Element", "p:last", ["first"] );
+               t( "Even Elements", "p:even", ["firstp","sndp","sap"] );
+               t( "Odd Elements", "p:odd", ["ap","en","first"] );
+               
+               t( "Position Equals", "p:eq(1)", ["ap"] );
+               t( "Position Greater Than", "p:gt(0)",
+                       ["ap","sndp","en","sap","first"] );
+               t( "Position Less Than", "p:lt(3)",
+                       ["firstp","ap","sndp"] );
+               
+               t( "Is A Parent", "p:parent", 
+                       ["firstp","ap","sndp","en","sap","first"] );
+               
+               t( "Is Visible", "input:visible", 
+                       ["text2","radio1","radio2","check1"] );
+               t( "Is Hidden", "input:hidden", 
+                       ["hidden","text1","check2"] );
+       </script></pre>
+</body>
+</html>
diff --git a/test/tests/prereq.html b/test/tests/prereq.html
new file mode 100644 (file)
index 0000000..e73f19b
--- /dev/null
@@ -0,0 +1,30 @@
+<html id="html">
+<head>
+       <script type="text/javascript" src="../lib/Test/Builder.js"></script>
+       <script type="text/javascript" src="../lib/Test/More.js"></script>
+       <script type="text/javascript" src="../lib/test.js"></script>
+       <script type="text/javascript" src="../../jquery/jquery-svn.js"></script>
+</head>
+<body id="body">
+       <h1>Test for Prerequisites</h1>
+
+       <pre id="test"><script>
+               plan({tests: 11});
+               
+               ok( Array.prototype.push, "Array.push()" );
+               ok( Function.prototype.apply, "Function.apply()" );
+               ok( document.getElementById, "getElementById" );
+               ok( document.getElementsByTagName, "getElementsByTagName" );
+               ok( RegExp, "RegExp" );
+               
+               ok( jQuery, "jQuery" );
+               ok( $, "$()" );
+
+               ok( $.find, "Select" );
+               ok( $.filter, "filter()" );
+               
+               ok( $.event.add, "addEvent" );
+               ok( $.event.remove, "removeEvent" );
+       </script></pre>
+</body>
+</html>
diff --git a/test/tests/xpath.html b/test/tests/xpath.html
new file mode 100644 (file)
index 0000000..483f896
--- /dev/null
@@ -0,0 +1,71 @@
+<html id="html">
+<head>
+       <script type="text/javascript" src="../lib/Test/Builder.js"></script>
+       <script type="text/javascript" src="../lib/Test/More.js"></script>
+       <script type="text/javascript" src="../lib/test.js"></script>
+       <script type="text/javascript" src="../../jquery/jquery-svn.js"></script>
+</head>
+<body id="body">
+       <h1>XPath Expressions</h1>
+       
+       <!-- Test HTML -->
+       <div id="main" style="display: none;">
+               <p id="firstp">See <a id="simon1" href="http://simon.incutio.com/archive/2003/03/25/#getElementsBySelector" rel="bookmark">this blog entry</a> 
+                       for more information.</p>
+               <p id="ap">Here are some links in a normal paragraph: 
+                       <a id="google" href="http://www.google.com/" title="Google!">Google</a>, 
+                       <a id="groups" href="http://groups.google.com/">Google Groups</a>. 
+                       This link has <code><a href="#" id="anchor1">class="blog"</a></code>: 
+                       <a href="http://diveintomark.org/" class="blog" hreflang="en" id="mark">diveintomark</a>
+               </p>
+               <div id="foo">
+                       <p id="sndp">Everything inside the red border is inside a div with 
+                       <code>id="foo"</code>.</p>
+                       <p lang="en" id="en">This is a normal link: 
+                       <a id="yahoo" href="http://www.yahoo.com/" class="blogTest">Yahoo</a>
+                       </p>
+                       <p id="sap">This link has <code><a href="#2" id="anchor2">class="blog"</a></code>: 
+                       <a href="http://simon.incutio.com/" class="blog link" id="simon">Simon Willison's Weblog</a>
+                       </p>
+               </div>
+               <p id="first">Try them out: </p>
+               <ol id="empty"></ol>
+               <form id="form">
+                       <input type="text" value="Test" id="text1"/>
+                       <input type="text" value="Test" id="text2" disabled="disabled"/>
+                       <input type="radio" name="radio1" id="radio1"/>
+                       <input type="radio" name="radio2" id="radio2" checked/>
+                       <input type="checkbox" name="check" id="check1" checked/>
+                       <input type="checkbox" name="check" id="check2"/>
+               </form>
+       </div>
+       
+       <pre id="test"><script>
+               plan({tests: 14});
+               
+               cmpOK( $.find("//*").length, ">=", 30, "All Elements (//*)" );
+               t( "All Div Elements", "//div", ["main","foo"] );
+               
+               t( "Absolute Path", "/html/body", ["body"] );
+               t( "Absolute Path w/ *", "/*/body", ["body"] );
+               t( "Long Absolute Path", "/html/body/div/div/p", ["sndp","en","sap"] );
+               
+               t( "Absolute and Relative Paths", "/html//div", ["main","foo"] );
+               
+               t( "All Children, Explicit", "//code/*", ["anchor1","anchor2"] );
+               t( "All Children, Implicit", "//code/", ["anchor1","anchor2"] );
+               
+               t( "Attribute Exists", "//a[@title]", ["google"] );
+               t( "Attribute Equals", "//a[@rel='bookmark']", ["simon1"] );
+               
+               t( "Parent Axis", "//p/..", ["main","foo"] );
+               t( "Sibling Axis", "//p/../", 
+                       ["firstp","ap","foo","first","empty","form","sndp","en","sap"] );
+               t( "Sibling Axis", "//p/../*", 
+                       ["firstp","ap","foo","first","empty","form","sndp","en","sap"] );
+                       
+               t( "Has Children", "//p[a]", 
+                       ["firstp","ap","en","sap"] );
+       </script></pre>
+</body>
+</html>