Index: tests/SimpleTest/SimpleTest.js
===================================================================
--- tests/SimpleTest/SimpleTest.js	(revision 0)
+++ tests/SimpleTest/SimpleTest.js	(revision 0)
@@ -0,0 +1,357 @@
+/**
+ * SimpleTest, a partial Test.Simple/Test.More API compatible test library.
+ *
+ * Why?
+ *
+ * Test.Simple doesn't work on IE < 6.
+ * TODO:
+ *  * Support the Test.Simple API used by MochiKit, to be able to test MochiKit 
+ * itself against IE 5.5
+ *
+ *    Things that stop MochiKit tests rigth now:
+ * 
+ *    * JSAN Support (For JSAN test) 
+ *
+ *    Things that require to modify MochiKit tests:
+ *
+ *    * Async tests: Use SimpleTest.waitForExplicitFinish() instead of 
+ *      Test.Builder().beginAsync.
+ *
+ *
+ * As the JSAN test is not currently included on the tests  index,
+ * I think that I can stay away of JSAN and its magic and keep the
+ * focus on getting Test.More functions supported.
+**/
+
+var SimpleTest = {};
+SimpleTest._tests = [];
+SimpleTest._stopOnLoad = true;
+
+/**
+ * Not implemented.
+**/
+SimpleTest.plan = function(){};
+
+/**
+ * Something like assert.
+**/
+SimpleTest.ok = function(condition, name, diag) {
+    SimpleTest._tests.push({'result': !!condition, 'name': name, 
+                            'diag': diag || ""});
+}
+
+/**
+ * Roughly equivalent to ok(a==b, name)
+**/
+SimpleTest.is = function (a, b, name) {
+    SimpleTest.ok(a == b, name, "expected " + a + ", got " + b);
+}
+
+
+/**
+ * Makes a test report, returns it as a DIV element.
+**/
+SimpleTest.report = function() {
+    var passed = 0;
+    var failed = 0;
+    var output = DIV({'class': 'tests_report'});
+    var results = [];
+    forEach(SimpleTest._tests, function(test) {
+        if(test.result) {
+            results.push(DIV({'class': 'test_ok'},
+                         "ok - " + test.name));
+            passed++;
+        } else {
+            results.push(DIV({'class': 'test_not_ok'},
+                             "not ok - " + test.name + " " + test.diag));
+            failed++;
+        }
+    });
+    return DIV({'class': 'tests_report'},
+                DIV({'class': 'tests_summary', 
+                     'style': {'backgroundColor': failed==0?'#0f0':'#f00'}},
+                    DIV({'class': 'tests_passed'},
+                        "Passed: " + passed),
+                    DIV({'class': 'tests_failed'},
+                        "Failed: " + failed)),
+                results);
+}
+
+/**
+ * Toggle element visibility
+**/
+SimpleTest.toggle = function(el) {
+    if (computedStyle(el, 'display') == 'block') {
+        el.style.display = 'none';
+    } else {
+        el.style.display = 'block';
+    }
+}
+
+/**
+ * Toggle visibility for divs with a specific class.
+**/
+SimpleTest.toggleByClass = function(cls) {
+    map(SimpleTest.toggle, getElementsByTagAndClassName('div', cls));
+}
+
+/**
+ * Shows the report in the browser
+**/
+
+SimpleTest.showReport = function() {
+    var togglePassed = A({'href': '#'}, "Toggle passed tests");
+    var toggleFailed = A({'href': '#'}, "Toggle failed tests");
+    togglePassed.onclick = partial(SimpleTest.toggleByClass, 'test_ok');
+    toggleFailed.onclick = partial(SimpleTest.toggleByClass, 'test_not_ok');
+    var body = document.getElementsByTagName("body")[0];
+    var firstChild = body.childNodes[0];
+    var addNode;
+    if (firstChild) {
+        addNode = function(el){body.insertBefore(el, firstChild)};
+    } else {
+        addNode = function(el){body.appendChild(el)};
+    }
+    addNode(togglePassed);
+    addNode(SPAN(null, " "));
+    addNode(toggleFailed);
+    addNode(SimpleTest.report());
+}
+
+/**
+ * Tells SimpleTest to don't finish the test when the document is loaded, useful
+ * for asynchronous tests.
+ *
+ * When SimpleTest.waitForExplicitFinish is called, explicit SimpleTest.finish() 
+ * is required.
+**/
+SimpleTest.waitForExplicitFinish = function() {
+    SimpleTest._stopOnLoad = false;
+}
+
+/**
+ * Talks to the TestRunner if being ran on a iframe and the parent has a 
+ * TestRunner object.
+**/
+SimpleTest.talkToRunner = function() {
+    if (parent && parent.TestRunner) {
+        parent.TestRunner.testFinished(document);
+    }
+}
+
+/**
+ * Finishes the tests. This is automatically called, except when 
+ * SimpleTest.waitForExplicitFinish() has been invoked.
+**/
+SimpleTest.finish = function() {
+    SimpleTest.showReport();
+    SimpleTest.talkToRunner();
+}
+
+
+addLoadEvent(function() {
+    if (SimpleTest._stopOnLoad) {
+        SimpleTest.finish();
+    }
+});
+
+//  --------------- Test.Builder/Test.More isDeeply() -----------------
+
+
+SimpleTest.DNE = { dne: 'Does not exist' };
+SimpleTest.LF = "\r\n";
+SimpleTest._isRef = function (object) {
+    var type = typeof(object);
+    return type == 'object' || type == 'function';
+};
+
+
+SimpleTest._deepCheck = function (e1, e2, stack, seen) {
+    var ok = false;
+    // Either they're both references or both not.
+    var sameRef = !(!SimpleTest._isRef(e1) ^ !SimpleTest._isRef(e2));
+    if (e1 == null && e2 == null) {
+        ok = true;
+    } else if (e1 != null ^ e2 != null) {
+        ok = false;
+    } else if (e1 == SimpleTest.DNE ^ e2 == SimpleTest.DNE) {
+        ok = false;
+    } else if (sameRef && e1 == e2) {
+        // Handles primitives and any variables that reference the same
+        // object, including functions.
+        ok = true;
+    } else if (SimpleTest.isa(e1, 'Array') && SimpleTest.isa(e2, 'Array')) {
+        ok = SimpleTest._eqArray(e1, e2, stack, seen);
+    } else if (typeof e1 == "object" && typeof e2 == "object") {
+        ok = SimpleTest._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;
+};
+
+SimpleTest._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 SimpleTest._eqAssoc(a1, a2, stack, seen);
+    for (var i = 0; i < max; i++) {
+        var e1 = i > a1.length - 1 ? SimpleTest.DNE : a1[i];
+        var e2 = i > a2.length - 1 ? SimpleTest.DNE : a2[i];
+        stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
+        if (ok = SimpleTest._deepCheck(e1, e2, stack, seen)) {
+            stack.pop();
+        } else {
+            break;
+        }
+    }
+    return ok;
+};
+
+SimpleTest._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 ? SimpleTest.DNE : o1[i];
+        var e2 = o2[i] == undefined ? SimpleTest.DNE : o2[i];
+        stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
+        if (ok = SimpleTest._deepCheck(e1, e2, stack, seen)) {
+            stack.pop();
+        } else {
+            break;
+        }
+    }
+    return ok;
+};
+
+SimpleTest._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:" + SimpleTest.LF;
+    for (var i = 0; i < vals.length; i++) {
+        var val = vals[i];
+        if (val == null) {
+            val = 'undefined';
+        } else {
+             val == SimpleTest.DNE ? "Does not exist" : "'" + val + "'";
+        }
+    }
+
+    out += vars[0] + ' = ' + vals[0] + SimpleTest.LF;
+    out += vars[1] + ' = ' + vals[1] + SimpleTest.LF;
+    
+    return '    ' + out;
+};
+
+
+SimpleTest.isDeeply = function (it, as, name) {
+    var ok;
+    // ^ is the XOR operator.
+    if (SimpleTest._isRef(it) ^ SimpleTest._isRef(as)) {
+        // One's a reference, one isn't.
+        ok = false;
+    } else if (!SimpleTest._isRef(it) && !SimpleTest._isRef(as)) {
+        // Neither is an object.
+        ok = SimpleTest.is(it, as, name);
+    } else {
+        // We have two objects. Do a deep comparison.
+        var stack = [], seen = [];
+        if ( SimpleTest._deepCheck(it, as, stack, seen)) {
+            ok = SimpleTest.ok(true, name);
+        } else {
+            ok = SimpleTest.ok(false, name, SimpleTest._formatStack(stack));
+        }
+    }
+    return ok;
+};
+
+SimpleTest.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;
+};
+
+SimpleTest.isa = function (object, clas) {
+    return SimpleTest.typeOf(object) == clas;
+};
+
+// Global symbols:
+var ok = SimpleTest.ok;
+var is = SimpleTest.is;
+var isDeeply = SimpleTest.isDeeply;
Index: tests/SimpleTest/TestRunner.js
===================================================================
--- tests/SimpleTest/TestRunner.js	(revision 0)
+++ tests/SimpleTest/TestRunner.js	(revision 0)
@@ -0,0 +1,115 @@
+/**
+ * TestRunner: A test runner for SimpleTest
+ * TODO:
+ * 
+ *  * Avoid moving iframes: That causes reloads on mozilla and opera.
+ *
+ *
+**/
+var TestRunner = {};
+TestRunner._iframes = {};
+TestRunner._iframeDocuments = {};
+TestRunner._iframeRows = {};
+TestRunner._currentTest = 0;
+TestRunner._urls = [];
+TestRunner._testsDiv = DIV();
+TestRunner._progressDiv = DIV();
+TestRunner._summaryDiv = DIV(null, 
+                             H1(null, "Tests Summary"),
+                             TABLE(null, 
+                                   THEAD(null, 
+                                         TR(null,
+                                            TH(null, "Test"), 
+                                            TH(null, "Passed"), 
+                                            TH(null, "Failed"))),
+
+                                    TBODY()));
+
+/**
+ * Toggle element visibility
+**/
+TestRunner._toggle = function(el) {
+    if (computedStyle(el, 'display') == 'block') {
+        el.style.display = 'none';
+    } else {
+        el.style.display = 'block';
+    }
+}
+
+
+/**
+ * Creates the iframe that contains a test
+**/
+TestRunner._makeIframe = function (url) {
+    var iframe = document.createElement('iframe');
+    iframe.setAttribute('src', url);
+    iframe.setAttribute('name', url);
+    iframe.setAttribute('width', '500');
+    var tbody = TestRunner._summaryDiv.getElementsByTagName("tbody")[0];
+    var tr = TR(null, TD({'colspan': '3'}, iframe));
+    iframe._row = tr;
+    tbody.appendChild(tr);
+    return iframe;
+}
+
+/**
+ * TestRunner entry point.
+ *
+ * The arguments are the URLs of the test to be ran.
+ *
+**/
+TestRunner.runTests = function (/*url...*/) {
+    var body = document.getElementsByTagName("body")[0];
+    appendChildNodes(body, TestRunner._testsDiv, TestRunner._progressDiv,
+                     TestRunner._summaryDiv);
+    for (var i = 0; i < arguments.length; i++) {
+        TestRunner._urls.push(arguments[i]); 
+    }
+    TestRunner.runNextTest();
+}
+
+
+/**
+ * Run the next test. If no test remains, calls makeSummary
+**/
+TestRunner.runNextTest = function() {
+    if (TestRunner._currentTest < TestRunner._urls.length) {
+        var url = TestRunner._urls[TestRunner._currentTest];
+        var progress = SPAN(null, "Running " + url + "...");
+        TestRunner._progressDiv.appendChild(progress);
+        TestRunner._iframes[url] = TestRunner._makeIframe(url);
+    }  else {
+        TestRunner.makeSummary();
+    }
+}
+
+/**
+ * This stub is called by SimpleTest when a test is finished.
+**/
+TestRunner.testFinished = function (doc) {
+    appendChildNodes(TestRunner._progressDiv, SPAN(null, "Done"), BR());
+    var finishedURL = TestRunner._urls[TestRunner._currentTest];
+    TestRunner._iframeDocuments[finishedURL] = doc;
+    TestRunner._iframes[finishedURL].style.display = "none";
+    TestRunner._currentTest++;
+    TestRunner.runNextTest();
+}
+
+/**
+ * Display the summary in the browser
+**/
+TestRunner.makeSummary = function() {
+    var rows = [];
+    for (var url in TestRunner._iframeDocuments) {
+        var doc = TestRunner._iframeDocuments[url];
+        var nOK = withDocument(doc, partial(getElementsByTagAndClassName, 
+                                             'div', 'test_ok')).length;
+        var nNotOK = withDocument(doc, partial(getElementsByTagAndClassName, 
+                                                'div', 'test_not_ok')).length;
+        var row = TR({'style': {'backgroundColor': nNotOK > 0 ? "#f00":"#0f0"}}, 
+                      TD(null, url), TD(null, nOK), TD(null, nNotOK));
+        row.onclick = partial(TestRunner._toggle, TestRunner._iframes[url]);
+        var tbody = TestRunner._summaryDiv.getElementsByTagName("tbody")[0];
+        tbody.insertBefore(row, TestRunner._iframes[url]._row)
+    }
+}
Index: tests/SimpleTest/test.css
===================================================================
--- tests/SimpleTest/test.css	(revision 0)
+++ tests/SimpleTest/test.css	(revision 0)
@@ -0,0 +1,20 @@
+.test_ok {
+	color: green;
+	display: none;
+}
+.test_not_ok {
+	color: red;
+	display: block;
+}
+
+.test_ok, .test_not_ok {
+   border-bottom-width: 2px;
+   border-bottom-style: solid;
+   border-bottom-color: black;
+}
+
+.tests_report {
+	border-width: 2px;
+	border-style: solid;
+	width: 20em;
+}


