// -*- test-case-name: nevow.test.test_javascript -*-

/**
 * Tests for Divmod.UnitTest, the Javascript unit-testing framework.
 * Uses mock test cases provided by Divmod.Test.Mock.
 */

// import Divmod.UnitTest
// import Divmod.Test.Mock
/* DON'T load Divmod.MockBrowser; it should be imported implicitly. */


/**
 * A mock L{TestResult} object that we use to test that L{startTest} and L{stopTest}
 * are called appropriately.
 */
Divmod.Test.TestUnitTest.MockResult = Divmod.Class.subclass('Divmod.Test.TestUnitTest.MockResult');
Divmod.Test.TestUnitTest.MockResult.methods(
    function __init__(self) {
        self.log = '';
    },

    function startTest(self, test) {
        self.log += 'startTest ' + test.id();
    },

    function addSuccess(self, test) {
        self.log += ' addSuccess ' + test.id();
    },

    function stopTest(self, test) {
        self.log += ' stopTest ' + test.id();
    });



/**
 * Tests for assertions in L{Divmod.UnitTest.TestCase}.
 */
Divmod.Test.TestUnitTest.AssertionTests = Divmod.UnitTest.TestCase.subclass('Divmod.Test.TestUnitTest.AssertionTests');
Divmod.Test.TestUnitTest.AssertionTests.methods(
    /**
     * Test that L{assert} raises an exception if its expression is false.
     */
    function test_assert(self) {
        self.assertThrows(Divmod.UnitTest.AssertionError,
                          function () { self.assert(false, "message"); })
    },

    /**
     * Verify that isTestCaseClass returns a positive result for TestCase
     * subclasses and a negative result for other types of object.
     */

    function test_isTestCaseClass(self) {
        self.assertIdentical(
            true, Divmod.UnitTest.isTestCaseClass(
                Divmod.Test.TestUnitTest.AssertionTests));
        self.assertIdentical(
            false, Divmod.UnitTest.isTestCaseClass(
                Divmod.Test.TestUnitTest.AssertionTests()));
        self.assertIdentical(
            false, Divmod.UnitTest.isTestCaseClass(1));
    },


    /**
     * Test that L{assertThrows} doesn't raise an exception if its callable
     * raises the excepted error.
     */
    function test_assertThrowsPositive(self) {
        try {
            self.assertThrows(Divmod.UnitTest.AssertionError,
                              function () {
                                  throw Divmod.UnitTest.AssertionError();
                              });
        } catch (e) {
            self.fail("assertThrows should have passed: " + e.message);
        }
    },


    /**
     * Test that L{assertThrows} raises an exception if its callable does
     * I{not} raise an exception.
     */
    function test_assertThrowsNoException(self) {
        var raised = true;
        try {
            self.assertThrows(Divmod.UnitTest.AssertionError,
                              function () { });
            raised = false;
        } catch (e) {
            if (!(e instanceof Divmod.UnitTest.AssertionError)) {
                self.fail("assertThrows should have thrown AssertionError");
            }
        }
        if (!raised) {
            self.fail("assertThrows did not raise an error");
        }
    },


    /**
     * Test that L{assertThrows} raises an exception if its callable raises
     * the wrong kind of exception.
     */
    function test_assertThrowsWrongException(self) {
        var raised = true;
        try {
            self.assertThrows(Divmod.UnitTest.AssertionError,
                              function () { throw Divmod.IndexError(); });
            raised = false;
        } catch (e) {
            if (!(e instanceof Divmod.UnitTest.AssertionError)) {
                self.fail("assertThrows should have thrown AssertionError");
            }
        }
        if (!raised) {
            self.fail("assertThrows did not raise an error");
        }
    },


    /**
     * Test that L{compare} does not raise an exception if its callable
     * returns C{true}.
     */
    function test_comparePositive(self) {
        self.compare(function () { return true; });
    },


    /**
     * Test that L{compare} raises an error if its callable returns C{false}.
     */
    function test_compareNegative(self) {
        self.assertThrows(Divmod.UnitTest.AssertionError,
                          function () {
                              self.compare(
                                  function (a, b) { return a === b; },
                                  "!==", "a", "b");
                          });
    },


    /**
     * Test that the message of L{compare}'s AssertionError describes the
     * failed the comparison based on its parameters.
     */
    function test_compareDefaultMessage(self) {
        try {
            self.compare(function () {return false;}, "<->", "a", "b");
        } catch (e) {
            self.assertIdentical(e.message, '"a" <-> "b"');
        }
    },


    /**
     * Test that the L{compare}'s AssertionError includes the optional
     * message if it is provided.
     */
    function test_compareWithMessage(self) {
        try {
            self.compare(function () {return false;}, "<->", "a", "b",
                         "Hello");
        } catch (e) {
            self.assertIdentical(e.message, '"a" <-> "b": Hello');
        }
    },


    /**
     * Test that L{assertIdentical} raises an exception if its arguments are
     * unequal, and that the message of the raised exception contains the
     * arguments.
     */
    function test_assertIdenticalNegative(self) {
        var e = self.assertThrows(Divmod.UnitTest.AssertionError,
                                  function () {
                                      self.assertIdentical('apple', 'orange');
                                  });
        self.assert(e.message === '"apple" !== "orange"', e.message);
    },


    /**
     * If L{assertIdentical} is given a message as an optional third argument,
     * that message should appear in the raised exception's message. Test this.
     */
    function test_assertIdenticalNegativeWithMessage(self) {
        try {
            self.assertIdentical('apple', 'orange', 'some message');
        } catch (e) {
            self.assert(e.message === '"apple" !== "orange": some message');
        }
    },


    /**
     * Test that L{assertIdentical} doesn't raise an exception if its
     * arguments are equal.
     */
    function test_assertIdenticalPositive(self) {
        self.assertIdentical('apple', 'apple');
    },


    /**
     * Test that L{assertIdentical} thinks that 1 and '1' are unequal.
     */
    function test_assertIdenticalDifferentTypes(self) {
        var raised = true;
        var e = self.assertThrows(Divmod.UnitTest.AssertionError,
                                  function () {
                                      self.assertIdentical(1, '1');
                                  });
        self.assert(e.message === '1 !== "1"');
    },


    /**
     * Test that L{assertArraysEqual} doesn't raise an exception if it is
     * passed that two 'equal' arrays.
     */
    function test_assertArraysEqualPositive(self) {
        self.assertArraysEqual([], []);
        self.assertArraysEqual([1, 2], [1, 2]);
    },


    /**
     * Test that L{assertArraysEqual} raises exceptions if it is passed two unequal
     * arrays.
     */
    function test_assertArraysEqualNegative(self) {
        self.assertThrows(Divmod.UnitTest.AssertionError,
                          function () {
                              self.assertArraysEqual([1, 2], [1, 2, 3]);
                          });
        self.assertThrows(Divmod.UnitTest.AssertionError,
                          function () {
                              self.assertArraysEqual({'foo': 2}, [2]);
                          });
        self.assertThrows(Divmod.UnitTest.AssertionError,
                          function () {
                              self.assertArraysEqual(1, [1]);
                          });
        self.assertThrows(Divmod.UnitTest.AssertionError,
                          function () {
                              self.assertArraysEqual(function () { return 1; },
                                                     function () { return 2; });
                          });
        self.assertThrows(Divmod.UnitTest.AssertionError,
                          function () {
                              self.assertArraysEqual(function () { },
                                                     function () { });
                          });
    },


    /**
     * Test that two equal arrays are not identical, and that an object is
     * identical to itself.
     */
    function test_assertIdentical(self) {
        var foo = [1, 2];
        self.assertIdentical(foo, foo);
        self.assertThrows(Divmod.UnitTest.AssertionError,
                          function () { self.assertIdentical(foo, [1, 2]); });
    });



/**
 * Tests for L{TestCase}.
 */
Divmod.Test.TestUnitTest.TestCaseTest = Divmod.UnitTest.TestCase.subclass('Divmod.Test.TestUnitTest.TestCaseTest');
Divmod.Test.TestUnitTest.TestCaseTest.methods(
    function setUp(self) {
        self.result = Divmod.UnitTest.TestResult();
    },

    /**
     * Test that when a test is run, C{setUp} is called first, then the test
     * method, then L{tearDown}.
     */
    function test_wasRun(self) {
        var test = Divmod.Test.Mock._WasRun("test_good");
        self.assertIdentical(test.log, '');
        test.run(self.result);
        self.assertIdentical(test.log, 'setUp test tearDown');
    },

    /**
     * Test that C{tearDown} still gets called, even when the test fails.
     */
    function test_tearDownCalled(self) {
        var test = Divmod.Test.Mock._WasRun("test_bad");
        test.run(self.result);
        self.assertIdentical(test.log, 'setUp tearDown');
    },

    /**
     * Test that C{run} takes a L{TestResult} that we can use to see whether
     * the test passed.
     */
    function test_goodResult(self) {
        var test = Divmod.Test.Mock._WasRun('test_good');
        test.run(self.result);
        self.assertArraysEqual(self.result.getSummary(), [1, 0, 0]);
        self.assert(self.result.wasSuccessful());
    },


    /**
     * Test that C{run} takes a L{TestResult} that we can use to see whether
     * test test failed.
     */
    function test_badResult(self) {
        var test = Divmod.Test.Mock._WasRun('test_bad');
        test.run(self.result);
        self.assertArraysEqual(self.result.getSummary(), [1, 1, 0]);
        self.assert(!self.result.wasSuccessful());
    },


    /**
     * Test that the L{TestResult} distinguishes between failed assertions
     * and general errors.
     */
    function test_errorResult(self) {
        var test = Divmod.Test.Mock._WasRun('test_error');
        test.run(self.result);
        self.assertArraysEqual(self.result.getSummary(), [1, 0, 1]);
        self.assert(!self.result.wasSuccessful());
    },


    /**
     * Test that we can find out which tests had which errors and which tests
     * succeeded.
     */
    function test_resultAccumulation(self) {
        var suite = Divmod.UnitTest.TestSuite();
        var bad = Divmod.Test.Mock._WasRun('test_bad');
        var good = Divmod.Test.Mock._WasRun('test_good');
        var error = Divmod.Test.Mock._WasRun('test_error');
        suite.addTests([bad, good, error]);
        suite.run(self.result);
        self.assertArraysEqual(self.result.getSummary(), [3, 1, 1]);
        // check the failure
        self.assertIdentical(self.result.failures[0].length, 2);
        self.assertIdentical(self.result.failures[0][0], bad);
        self.assert(self.result.failures[0][1]
                    instanceof Divmod.UnitTest.AssertionError);
        self.assertIdentical(self.result.failures[0][1].message,
                             "fail this test deliberately");
        // check the error
        self.assertIdentical(self.result.errors[0].length, 2);
        self.assertIdentical(self.result.errors[0][0], error);
        self.assert(self.result.errors[0][1] instanceof Divmod.Error);
        self.assertIdentical(self.result.errors[0][1].message, "error");
        self.assertArraysEqual(self.result.successes, [good]);
    },


    /**
     * Test that neither L{tearDown} nor the test method is called when
     * L{setUp} fails.
     */
    function test_failureInSetUp(self) {
        var test = Divmod.Test.Mock._BadSetUp('test_method');
        self.assertIdentical(test.log, '');
        test.run(self.result);
        self.assertIdentical(test.log, '');
    },


    /**
     * Test that failures in L{setUp} are reported to the L{TestResult}
     * object.
     */
    function test_failureInSetUpReported(self) {
        var test = Divmod.Test.Mock._BadSetUp('test_method');
        test.run(self.result);
        self.assertArraysEqual(self.result.getSummary(), [1, 0, 1]);
    },


    /**
     * Test that failures in L{tearDown} are reported to the L{TestResult}
     * object.
     */
    function test_failureInTearDownReported(self) {
        var test = Divmod.Test.Mock._BadTearDown('test_method');
        test.run(self.result);
        self.assertArraysEqual(self.result.getSummary(), [1, 0, 1]);
    },


    /**
     * Test that a test which fails in L{tearDown} does *not* get added as
     * a success.
     */
    function test_badTearDownNotSuccess(self) {
        var test = Divmod.Test.Mock._BadTearDown('test_method');
        test.run(self.result);
        self.assertIdentical(self.result.successes.length, 0);
    },


    /**
     * Test that L{TestCase.run} calls L{TestResult.startTest} and
     * L{TestResult.stopTest}.
     */
    function test_startAndStopTest(self) {
        var test = Divmod.Test.Mock._WasRun('test_good');
        var id = test.id();
        var result = Divmod.Test.TestUnitTest.MockResult();
        test.run(result);
        self.assertIdentical(
            result.log,
            'startTest ' + id + ' addSuccess ' + id + ' stopTest ' + id);
    },


    /**
     * Test that we can create a L{TestSuite}, add tests to it, run it and
     * get the results of all of the tests.
     */
    function test_testSuite(self) {
        var suite = Divmod.UnitTest.TestSuite();
        suite.addTest(Divmod.Test.Mock._WasRun('test_good'));
        suite.addTest(Divmod.Test.Mock._WasRun('test_bad'));
        suite.run(self.result);
        self.assertArraysEqual(self.result.getSummary(), [2, 1, 0]);
        self.assert(!self.result.wasSuccessful());
    },


    /**
     * Check that C{countTestCases} returns 0 for an empty suite, 1 for a test,
     * and n for a suite with n tests.
     */
    function test_countTestCases(self) {
        self.assertIdentical(self.countTestCases(), 1);
        var suite = Divmod.UnitTest.TestSuite();
        self.assertIdentical(suite.countTestCases(), 0);
        suite.addTest(self);
        self.assertIdentical(suite.countTestCases(), 1);
        suite.addTest(Divmod.Test.Mock._WasRun('good'));
        self.assertIdentical(suite.countTestCases(), 2);
    },


    /**
     * Test that C{id} returns the fully-qualified name of the test.
     */
    function test_id(self) {
        var test = Divmod.Test.Mock._WasRun('test_good');
        self.assertIdentical(test.id(), 'Divmod.Test.Mock._WasRun.test_good');
    },


    /**
     * Test that C{visit} calls the visitor with the test case.
     */
    function test_visitCase(self) {
        var log = [];
        function visitor(test) {
            log.push(test);
        }
        self.visit(visitor);
        self.assertArraysEqual(log, [self]);
    },


    /**
     * Test that C{visit} calls the visitor for each test case in a suite.
     */
    function test_visitSuite(self) {
        var log = [];
        function visitor(test) {
            log.push(test);
        }
        Divmod.UnitTest.TestSuite().visit(visitor);
        self.assertArraysEqual(log, []);
        var tests = [self, Divmod.Test.Mock._WasRun('test_good')];
        var suite = Divmod.UnitTest.TestSuite(tests);
        suite.visit(visitor);
        self.assertArraysEqual(log, tests);
    },


    /**
     * Check that issubclass returns true when the first parameter is a subclass
     * of the second, and false otherwise.
     */
    function test_issubclass(self) {
        self.assert(self.__class__.subclassOf(self.__class__),
                    "Thing should subclass itself");
        self.assert(self.__class__.subclassOf(Divmod.UnitTest.TestCase));
        self.assert(!Divmod.UnitTest.TestCase.subclassOf(self.__class__));
    });



Divmod.Test.TestUnitTest.LoaderTests = Divmod.UnitTest.TestCase.subclass("Divmod.Test.TestUnitTest.LoaderTests");
Divmod.Test.TestUnitTest.LoaderTests.methods(
    /**
     * Return a list containing the id() of each test in a suite.
     */
    function getTestIDs(self, suite) {
        var ids = [];
        suite.visit(function (test) { ids.push(test.id()); });
        return ids;
    },


    /**
     * Test that C{loadFromClass} returns an empty suite when given a
     * C{TestCase} subclass that contains no tests.
     */
    function test_loadFromClassEmpty(self) {
        var suite = Divmod.UnitTest.loadFromClass(Divmod.UnitTest.TestCase);
        self.assertArraysEqual(self.getTestIDs(suite), []);
    },


    /**
     * Test that C{loadFromClass} returns a suite which contains all the
     * test methods in a given C{TestCase} subclass.
     */
    function test_loadFromClass(self) {
        var suite = Divmod.UnitTest.loadFromClass(Divmod.Test.Mock._WasRun);
        self.assertArraysEqual(self.getTestIDs(suite),
                               ['Divmod.Test.Mock._WasRun.test_bad',
                                'Divmod.Test.Mock._WasRun.test_error',
                                'Divmod.Test.Mock._WasRun.test_good']);
    },


    /**
     * Test that C{loadFromModule} returns an empty suite when given a module
     * with no unit tests.
     */
    function test_loadFromModuleEmpty(self) {
        var module = {};
        var suite = Divmod.UnitTest.loadFromModule(module);
        self.assertIdentical(suite.countTestCases(), 0);
    },


    /**
     * Test that C{loadFromModule} returns a suite which contains all the
     * test methods in a given module.
     */
    function test_loadFromModule(self) {
        var Mock = {};
        Mock.SomeTestCase = Divmod.UnitTest.TestCase.subclass('Mock.SomeTestCase');
        Mock.SomeTestCase.methods(function test_method(self) {});
        suite = Divmod.UnitTest.loadFromModule(Mock);
        self.assertArraysEqual(self.getTestIDs(suite),
                               ['Mock.SomeTestCase.test_method']);
    });


Divmod.Test.TestUnitTest.RunnerTest = Divmod.UnitTest.TestCase.subclass('Divmod.Test.TestUnitTest.RunnerTest');
Divmod.Test.TestUnitTest.RunnerTest.methods(
    function setUp(self) {
        self.result = Divmod.UnitTest.TestResult();
    },


    /**
     * Test that the summary of an empty result object indicates the 'test run'
     * passed, and that no tests were run.
     */
    function test_formatSummaryEmpty(self) {
        self.assertIdentical(Divmod.UnitTest.formatSummary(self.result),
                             "PASSED (tests=0)");
    },


    /**
     * Test that the summary of a result object from a successful test run
     * indicates that the run was successful along with the number of tests in
     * the run.
     */
    function test_formatSummaryOK(self) {
        var test = Divmod.Test.Mock._WasRun('test_good');
        test.run(self.result);
        self.assertIdentical(Divmod.UnitTest.formatSummary(self.result),
                             "PASSED (tests=1)");
    },


    /**
     * Test that the summary of a result object from a test run with failures
     * indicates an overall failure as well as the number of test failures.
     */
    function test_formatSummaryFailed(self) {
        var test = Divmod.Test.Mock._WasRun('test_bad');
        test.run(self.result);
        self.assertIdentical(Divmod.UnitTest.formatSummary(self.result),
                             "FAILED (tests=1, failures=1)");
    },


    /**
     * As L{test_formatSummaryFailed}, but for errors instead of failures.
     */
    function test_formatSummaryError(self) {
        var test = Divmod.Test.Mock._WasRun('test_error');
        test.run(self.result);
        self.assertIdentical(Divmod.UnitTest.formatSummary(self.result),
                             "FAILED (tests=1, errors=1)");
    },


    /**
     * Sanity test added to make sure the summary makes sense when a suite
     * has both failed and errored tests.
     */
    function test_formatSummaryMultiple(self) {
        var test = Divmod.UnitTest.loadFromClass(Divmod.Test.Mock._WasRun);
        test.run(self.result);
        self.assertIdentical(Divmod.UnitTest.formatSummary(self.result),
                             "FAILED (tests=3, errors=1, failures=1)");
    },


    /**
     * Check that L{formatErrors} returns an empty string for an empty result.
     */
    function test_formatErrorsEmpty(self) {
        self.assertIdentical(Divmod.UnitTest.formatErrors(self.result), '');
    },


    /**
     * Check that L{formatErrors} returns an empty string for a successful result.
     */
    function test_formatErrorsOK(self) {
        var test = Divmod.Test.Mock._WasRun('test_good');
        test.run(self.result);
        self.assertIdentical(Divmod.UnitTest.formatErrors(self.result), '');
    },


    /**
     * Check that the L{formatError} returns a nicely formatted representation
     * of a failed/errored test.
     */
    function test_formatError(self) {
        var test = Divmod.Test.Mock._WasRun('test_bad');
        var error, failure;
        try {
            throw Divmod.Error("error-message");
        } catch (e) {
            error = e;
            failure = Divmod.Defer.Failure(error);
        }
        self.assertIdentical(
            Divmod.UnitTest.formatError('FAILURE', test, error),
            '[FAILURE] Divmod.Test.Mock._WasRun.test_bad: error-message\n'
            + failure.toPrettyText(failure.filteredParseStack()) + '\n');
    },


    /**
     * Check that L{formatErrors} returns a string that contains all of the
     * errors and failures from the result, formatted using L{formatError}.
     */
    function test_formatErrors(self) {
        var test = Divmod.UnitTest.loadFromClass(Divmod.Test.Mock._WasRun);
        test.run(self.result);
        var expected = '';
        for (var i = 0; i < self.result.errors.length; ++i) {
            expected += Divmod.UnitTest.formatError('ERROR',
                                                self.result.errors[i][0],
                                                self.result.errors[i][1]);
        }
        for (var i = 0; i < self.result.failures.length; ++i) {
            expected += Divmod.UnitTest.formatError('FAILURE',
                                                self.result.failures[i][0],
                                                self.result.failures[i][1]);
        }
        var observed = Divmod.UnitTest.formatErrors(self.result);
        self.assertIdentical(observed, expected);
    });

Divmod.Test.TestUnitTest.MockDocumentTest = Divmod.UnitTest.TestCase.subclass(
    'Divmod.Test.TestUnitTest.MockDocumentTest');

Divmod.Test.TestUnitTest.MockDocumentTest.methods(
    /**
     * Verify that the document is implicitly imported by the test set up.
     * NB: If we ever have a way to run these tests in a real browser, there
     * should be a way to skip this one.
     */
    function test_documentExists(self) {
        self.assert(document instanceof Divmod.MockBrowser.Document);
    },

    /**
     * Verify that the document has a "body" attribute which is an element.
     */
    function test_documentHasBody(self) {
        self.assert(document.body instanceof Divmod.MockBrowser.Element);
    },

    /**
     * Verify that the elements created by the document have their tag name
     * set based on input.
     */
    function test_createElement(self) {
        var aDocument = Divmod.MockBrowser.Document();
        self.assertIdentical(aDocument.createElement('something').tagName,
                             'SOMETHING');
    },

    /**
     * Elements created with L{Document.createElement} should have a
     * C{ownerDocument} property which refers to the document which created
     * them.
     */
    function test_createdElementHasDocument(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement('a');
        self.assertIdentical(anElement.ownerDocument, aDocument);
    },

    /**
     * Verify that text nodes are created with appropriate 'length',
     * 'nodeValue', and (empty) 'childNodes' attributes.
     */

    function test_createTextNode(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var aNode = aDocument.createTextNode("hello, world!");
        self.assertIdentical(aNode.length, 13);
        self.assertIdentical(aNode.nodeValue, "hello, world!");
        self.assertIdentical(aNode.childNodes.length, 0);
    },

    /**
     * Text Nodes created with L{Document.createElement} should have a
     * C{ownerDocument} property which refers to the document which created
     * them.
     */
    function test_createdTextNodeHasDocument(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var aNode = aDocument.createTextNode("hello, world!");
        self.assertIdentical(aNode.ownerDocument, aDocument);
    },

    /**
     * Verify that setting an attribute on an element results in that
     * attribute being returned from getAttribute.
     */
    function test_setGetAttribute(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement('test');
        anElement.setAttribute("abc", "123");
        self.assertIdentical(anElement.getAttribute("abc"), "123");
    },

    /**
     * Verify that setting an attribute and then removing it results in
     * getAttribute subsequently returning C{undefined}.
     */
    function test_getRemoveAttribute(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement('test');
        anElement.setAttribute("abc", "123");
        anElement.removeAttribute("abc");
        self.assertIdentical(anElement.getAttribute("abc"), undefined);
    },

    /**
     * Verify that an Element will have a "style" attribute that can be
     * manipulated.
     */
    function test_elementHasStyle(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement('test');
        anElement.style.border = 'thin solid blue';
        self.assertIdentical(anElement.style.border, 'thin solid blue');
    },

    /**
     * Verify that an Element given a certain class via setAttribute will be
     * given a 'className' attribute that matches the class.
     */
    function test_firefoxStyleClassAttribute(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement('test');
        anElement.setAttribute("class", "something");
        self.assertIdentical(anElement.className, "something");
    },

    /**
     * Verify that a mock element's appendChild() puts that element into the
     * childNodes array.
     */
    function test_appendChild(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement("test");
        var anotherElement = aDocument.createElement('test2');
        anElement.appendChild(anotherElement);
        self.assertIdentical(anElement.childNodes[0], anotherElement);
        self.assertIdentical(anotherElement.parentNode, anElement);
    },

    /**
     * Verify that a mock element's removeChild() removes that element (and
     * only that element) from the childNodes array.
     */
    function test_removeChild(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement("test");
        var anotherElement = aDocument.createElement("test2");
        var ignoreMe = aDocument.createElement("testX");
        var ignoreMe2 = aDocument.createElement("testY");
        anElement.appendChild(ignoreMe);
        anElement.appendChild(anotherElement);
        anElement.appendChild(ignoreMe2);
        anElement.removeChild(anotherElement);

        self.assertIdentical(anElement.childNodes.length, 2);
        for (var i = 0; i < anElement.childNodes.length; i++) {
            self.assert(anElement.childNodes[i] !== anotherElement);
        }
    },

    /**
     * Verify that an exception will be raised if a mock element is asked to
     * remove a child which it does not contain.
     */
    function test_removeChildInvalid(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement("test");
        var anotherElement = aDocument.createElement("test2");
        self.assertThrows(
            Divmod.MockBrowser.DOMError,
            function () {
                anElement.removeChild(anotherElement);
            });
    },

    /**
     * Verify that elements can be retrieved by their ID.
     */
    function test_getElementById(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement("a");
        var anotherElement = aDocument.createElement("b");
        aDocument.body.appendChild(anElement);
        aDocument.body.appendChild(anotherElement);

        anotherElement.id = 'test1';
        self.assertIdentical(aDocument.getElementById('test1'), anotherElement);
        var thirdElement = aDocument.createElement('c');
        aDocument.body.appendChild(thirdElement);
        thirdElement.id = 'test2';
        self.assertIdentical(aDocument.getElementById('test2'), thirdElement);
    },

    /**
     * Verify that orphaned elements (those not in any document) will not be
     * returned by getElementById even if they have an ID that matches.
     */
    function test_getElementByIdNoOrphans(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement("a");
        anElement.id = 'test1';
        self.assertIdentical(aDocument.getElementById('test1'), null);
        aDocument.body.appendChild(anElement);
        self.assertIdentical(aDocument.getElementById('test1'), anElement);
    },

    /**
     * Verify that conflicting IDs are resolved in the reverse order they are
     * created; i.e. that the most recently added created node in the document
     * with a given ID is found.
     *
     * This behavior is inconsistent with actual browser DOM, but providing
     * proper DOM semantics is difficult due to the fact that tests are
     * sharing lots of global state but should not be.
     *
     * Although these tests here each create their own document object for
     * better isolation, third-party code designed to run in an actual browser
     * (c.f. MochiKit.DOM) will generally assume that there is only one global
     * document and may hold their own references to it.
     *
     * Browsers appear to treat the first element to be added to the document
     * with a given ID as the definitive one, whereas we always want to take
     * the most recent, because a previous test may have stomped on that ID.
     *
     * Eventually it might be a better idea to have the test framework
     * interact with the global document a bit, re-setting it to a pristine
     * state between tests to that they do not interact with each other, and
     * correcting this implementation to be completely consistent with the
     * browser's getElementById.
     *
     * However, for the time being, this point is mostly moot; I can't find a
     * description of the behavior of ID conflicts in either the DOM
     * specifications or browser documentation (except by the vague
     * implication that they should never, ever happen) so this behavior is
     * correct inasmuch as it prevents tests from having inter-test
     * interactions which they should not have. -glyph
     */

    function test_conflictResolutionOrder(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var c = aDocument.createElement("c");
        var b = aDocument.createElement("b");
        var a = aDocument.createElement("a");

        c.id = 'test1';

        aDocument.body.appendChild(a);
        aDocument.body.appendChild(b);
        aDocument.body.appendChild(c);

        self.assertIdentical(aDocument.getElementById('test1'), c);
        b.id = 'test1';
        a.id = 'test1';

        // a was created later.
        self.assertIdentical(aDocument.getElementById("test1"), a);
    },

    /**
     * Verify that getElementById with an id that does not exist will mimic
     * Firefox's behavior and return 'null'.
     */
    function test_getNoElementById(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement("div");
        anElement.id = "some-id";
        self.assertIdentical(aDocument.getElementById("some-other-id"), null);
    },

    /**
     * Make a L{Document} containing some C{<span>} and C{<div>} nodes at
     * various depths.
     */
    function _makeNestedDocument(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var outerNode = aDocument.createElement("div");
        var innerNode = aDocument.createElement("div");
        var spanOne = aDocument.createElement("span");
        spanOne.appendChild(aDocument.createTextNode("LULZ"));
        innerNode.appendChild(spanOne);
        outerNode.appendChild(innerNode);
        var spanTwo = aDocument.createElement("span");
        spanTwo.appendChild(aDocument.createTextNode("OK"));
        outerNode.appendChild(spanTwo);
        aDocument.body.appendChild(outerNode);
        return aDocument;
    },

    /**
     * getElementsByTagName, when called on L{Document}, should return all
     * nodes with the specified tag name, at any depth inside the document's
     * body.
     */
    function test_getElementsByTagNameDocument(self) {
        var aDocument = self._makeNestedDocument();
        var outerNode = aDocument.body.childNodes[0];
        var deepSpan = outerNode.childNodes[0].childNodes[0];
        var shallowSpan = outerNode.childNodes[1];
        var result = aDocument.getElementsByTagName('span');
        self.assertIdentical(result.length, 2);
        self.assertIdentical(result[0], deepSpan);
        self.assertIdentical(result[1], shallowSpan);
    },

    /**
     * getElementsByTagName, when called on L{Document.body}, should exclude
     * its body node from the result.
     */
    function test_getElementsByTagNameBodyNoBody(self) {
        var aDocument = Divmod.MockBrowser.Document();
        self.assertIdentical(
            aDocument.body.getElementsByTagName('body').length, 0);
    },

    /**
     * getElementsByTagName, when called on L{Document}, should include its
     * body node from the result.
     */
    function test_getElementsByTagNameDocumentIncludesBody(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var nodes = aDocument.getElementsByTagName('body');
        self.assertIdentical(nodes.length, 1);
        self.assertIdentical(nodes[0], aDocument.body);
    },

    /**
     * getElementsByTagName should also work on L{Element}.
     */
    function test_getElementsByTagNameElement(self) {
        var aDocument = self._makeNestedDocument();
        var outerNode = aDocument.body.childNodes[0];
        var deepSpan = outerNode.childNodes[0].childNodes[0];
        var shallowSpan = outerNode.childNodes[1];
        var result = outerNode.getElementsByTagName('span');
        self.assertIdentical(result.length, 2);
        self.assertIdentical(result[0], deepSpan);
        self.assertIdentical(result[1], shallowSpan);
    },

    /**
     * getElementsByTagName, when called on L{Element}, should exclude the
     * node it's called on from the result.
     */
    function test_getElementsByTagNameElementNoTop(self) {
        var aDocument = self._makeNestedDocument();
        var shallowDiv = aDocument.body.childNodes[0];
        var deepDiv = shallowDiv.childNodes[0];
        var result = shallowDiv.getElementsByTagName('div');
        self.assertIdentical(result.length, 1);
        self.assertIdentical(result[0], deepDiv);
    },

    /**
     * getElementsByTagName should return all elements (no text nodes) when
     * passed C{*}.
     */
    function test_getElementsByTagNameWildcard(self) {
        var aDocument = self._makeNestedDocument();
        var result = aDocument.getElementsByTagName('*');
        self.assertIdentical(result.length, 5);
        self.assertIdentical(result[0], aDocument.body);
        var outerNode = aDocument.body.childNodes[0];
        self.assertIdentical(result[1], outerNode);
        var innerNode = outerNode.childNodes[0];
        self.assertIdentical(result[2], innerNode);
        var spanOne = innerNode.childNodes[0];
        self.assertIdentical(result[3], spanOne);
        var spanTwo = outerNode.childNodes[1];
        self.assertIdentical(result[4], spanTwo);

        result = outerNode.getElementsByTagName('*');
        self.assertIdentical(result.length, 3);
        self.assertIdentical(result[0], innerNode);
        self.assertIdentical(result[1], spanOne);
        self.assertIdentical(result[2], spanTwo);
    },

    /**
     * Verify that the string representation of a mock element captures all of
     * its interesting features to provide something to introspect while
     * debugging.
     */
    function test_elementToString(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement("stuff");
        // Tag name.
        self.assertIdentical(
            anElement.toString(), "<{MOCK}STUFF />");
        anElement.setAttribute("hello", "world");
        // Attributes.
        self.assertIdentical(
            anElement.toString(), '<{MOCK}STUFF hello="world" />');
        anElement.style.border = "thick solid blue";
        // Special "style" attribute.
        self.assertIdentical(
            anElement.toString(),
            '<{MOCK}STUFF hello="world" {style}="border: thick solid blue; " />');
        anotherElement = aDocument.createElement("other");
        anElement.appendChild(anotherElement);
        // Child nodes.
        self.assertIdentical(
            anElement.toString(),
            '<{MOCK}STUFF hello="world" {style}="border: thick solid blue; ">...</STUFF>');
    },

    /**
     * Verify that the default size of an Element will initially be (0, 0)
     * until that element is added to the document.
     */
    function test_defaultElementSize(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement("stuff");

        self.assertIdentical(0, anElement.clientHeight);
        self.assertIdentical(0, anElement.clientWidth);

        aDocument.body.appendChild(anElement);

        self.assertIdentical(aDocument.DEFAULT_HEIGHT,
                             anElement.clientHeight);
        self.assertIdentical(aDocument.DEFAULT_WIDTH,
                             anElement.clientWidth);
    },

    /**
     * Verify that the platform will respect the sizes set for mock elements
     * in tests.
     */
    function test_getMockElementSize(self) {
        var aDocument = Divmod.MockBrowser.Document();
        var anElement = aDocument.createElement("stuff");
        anElement.setMockElementSize(1234, 5678);

        var size = Divmod.Runtime.theRuntime.getElementSize(anElement);

        self.assertIdentical(size.w, 1234);
        self.assertIdentical(size.h, 5678);
    });


/**
 * Tests for L{Divmod.Runtime.Platform.repr}.
 */
Divmod.Test.TestUnitTest.ReprTests =
    Divmod.UnitTest.TestCase.subclass('Divmod.Test.TestUnitTest.ReprTests');
Divmod.Test.TestUnitTest.ReprTests.methods(
    /**
     * Test that repr(undefined) and repr(null) work.
     */
    function test_undefinedAndNull(self) {
        var repr = Divmod.UnitTest.repr;
        self.assertIdentical(repr(null), 'null');
        self.assertIdentical(repr(undefined), 'undefined');
    },

    /**
     * Test that some simple values have a reasonable repr().
     */
    function test_simpleValues(self) {
        var repr = Divmod.UnitTest.repr;
        self.assertIdentical(repr(5), '5');
        self.assertIdentical(repr('foo'), '"foo"');
        self.assert(repr(['foo']).search('foo') >= 0);
    });
