#             Perforce Defect Tracking Integration Project
#              <http://www.ravenbrook.com/project/p4dti/>
#
#          TEST_CHECK_XHTML.PY -- UNIT TEST FOR XHTML CHECKER
#
#             Gareth Rees, Ravenbrook Limited, 2001-05-06
#
#
# 1. INTRODUCTION
#
# This Python module is a unit test case for the check_xhtml Python module that
# checks XHTML documents against the XHTML 1.0 Transitional specification.
#
# The tests are purely regression tests.  The test cases feed documents to the
# XHTML checker, collect the output of the checker, and compare the actual
# output to the expected output.

import StringIO
import check_xhtml
import unittest

class case(unittest.TestCase):
    def __init__(self, name, text, error, methodName='runTest'):
        self.name = name
        self.text = text
        self.error = error
        unittest.TestCase.__init__(self, methodName)

    def shortDescription(self):
        return self.name + " (test_check_xhtml)"

    def runTest(self):
        text_stream = StringIO.StringIO(self.text)
        error_stream = StringIO.StringIO()
        h = check_xhtml.handler("Test", error_stream = error_stream)
        h.check(text_stream)
        if error_stream.getvalue() != self.error:
            self.fail("XHTML document:\n%s\n%s\n%s\nExpected errors:\n%s%s\nFound errors:\n%s" % ("-" * 70, self.text, "-" * 70, self.error, "-" * 70, error_stream.getvalue()))


start = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head><title>Test document</title></head>
<body>
"""

end = "\n</body></html>"

test_cases = [

    ('Unknown element',
     start + "<foo/>" + end,
     "Test(6) [1] <foo> element is not legal in XHTML 1.0 Transitional.\n"),

    ('Element not allowed in element',
     start + "<ul><p>Foo</p></ul>" + end,
     "Test(6) [2] Element <p> appears in <ul> (not allowed).\n"),

    ('Unknown attribute',
     start + '<p foo="bar">Foo</p>' + end,
     "Test(6) [4] Attribute 'foo' is not allowed in <p>.\n"),

    ('Id attribute but no name',
     start + '<a id="foo">Foo</a>' + end,
     "Test(6) [5] <a> element has 'id' attribute but no 'name' attribute.\n"),

    ('Name attribute but no id',
     start + '<a name="foo">Foo</a>' + end,
     "Test(6) [6] <a> element has 'name' attribute but no 'id' attribute.\n"),

    ('Id and name attributes differ',
     start + '<a name="foo" id="bar">Foo</a>' + end,
     "Test(6) [7] <a> element has id 'bar' but name 'foo'.\n"),

    ('Empty element contains character data',
     start + '<br>foo</br>' + end,
     "Test(6) [8] <br> element contains character data 'foo'.\n"),

    ('Element may not be empty',
     start + '<ol></ol>' + end,
     "Test(6) [9] <ol> element is empty.\n"),

    ('Unknown value for attribute',
     start + '<br clear="foo"/>' + end,
     "Test(6) [10] Attribute 'clear' for element <br> has value 'foo' (must be one of [left, all, right, none]).\n"),

    ('Attribute value not a number',
     start + '<ol> <li value="foo"></li> </ol>' + end,
     "Test(6) [11] Attribute 'value' for element <li> has illegal value 'foo' (not a Number).\n"),

    ('Duplicate id',
     start + '<a id="Foo" name="Foo">Foo</a>\n'
     '<a id="Foo" name="Foo">Bar</a>' + end,
     "Test(7) [12] Duplicate id 'Foo' (original on line 6).\n"),

    ('Missing required attribute',
     start + '<img height="10" width="10" src="foo"/>' + end,
     "Test(6) [13] Attribute 'alt' is required for <img> but not present.\n"),

    ('Table element has two captions',
     start + '<table><caption>Foo</caption><caption>Bar</caption><tr><td>Foo</td></tr></table>' + end,
     "Test(6) [15] Contents of <table> element [caption, caption, tr] doesn't match XHTML specification (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+)).\n"),

    ('Table element has no rows',
     start + '<table><caption>Foo</caption></table>' + end,
     "Test(6) [15] Contents of <table> element [caption] doesn't match XHTML specification (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+)).\n"),

    ('Table element mixes col and colgroup',
     start + '<table> <col /> <colgroup> <col /> </colgroup> <tbody><tr><td></td></tr></tbody> </table>' + end,
     "Test(6) [15] Contents of <table> element [col, colgroup, tbody] doesn't match XHTML specification (caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+)).\n"),

    ('Anchor in anchor',
     start + '<a href="foo"><b><a href="bar">baz</a></b></a>' + end,
     "Test(6) [16] Element <a> appears below <a> on line 6 (not allowed).\n"),

    ('Section anchor has no id',
     start + '<h1>Title</h1>\n<h2><a>Heading</a></h2>' + end,
     "Test(7) [17] Section anchor has no 'id' attribute.\n"),

    ('Section anchor inconsistent with heading level',
     start + '<h1>Title</h1>\n<h2><a id="section-1" name="section-1">1. Heading</a></h2>\n<h3><a id="section-2" name="section-2">Heading</a></h3>' + end,
     "Test(8) [18] Anchor for <h3> has id 'section-2': should look like 'section-1.1'.\n"),

    ('Missing section anchor',
     start + '<h1>Title</h1>\n<h2><a id="section-1" name="section-1">1. Heading</a></h2>\n<h3><a id="section-1.1" name="section-1.1">1.1. Heading</a></h3>\n<h4>Example</h4>' + end,
     "Test(9) [19] <h4> has no section anchor.\n"),

    ('Table cell with valign attribute',
     start + '<table><tr><td valign="top">Foo</td></tr></table>' + end,
     "Test(6) [20] <td> has valign attribute: better in the <tr>.\n"),

    ('Wrong reference anchor',
     start + '<h1>Title</h1>\n<h2><a id="section-A" name="section-A">A. References</a></h2>\n<table>\n<tr><td><a id="GDR-1971-01-02" name="GDR-1971-01-02">GDR 1971-01-02</a></td></tr>\n</table>' + end,
     "Test(9) [21] Reference anchor has id 'GDR-1971-01-02': should start with 'ref-'.\n"),

    ('Cross reference has no target',
     start + '<a href="#no-target">Foo</a>' + end,
     "Test(6) [22] Cross-reference '#no-target' has no target.\n"),

    ('Cross reference should be link',
     start + '<h1>Title</h1>\n<p>[<a href="#ref-GDR-1971-01-03">GDR 1971-01-03</a>]</p>\n<h2><a id="section-A" name="section-A">A. References</a></h2>\n<table>\n<tr valign="top"><td>[<a id="ref-GDR-1971-01-03" name="ref-GDR-1971-01-03" href="http://foo.invalid/">GDR 1971-01-03</a>]</td><td>Foo</td></tr>\n</table>' + end,
     "Test(7) [23] Cross-reference '#ref-GDR-1971-01-03' to references section should link to target 'http://foo.invalid/' instead.\n"),

    ('Inconsistent heading sequence',
     start + '<h1>Title</h1>\n<h2><a id="section-2" name="section-2">2. Foo</a></h2>\n<h4><a id="section-2.1.1" name="section-2.1.1">2.1.1. Bar</a></h4>' + end,
     "Test(8) [25] <h4> follows <h2>.\n"),

    ('Missing attributes for img element',
     start + '<img src="foo.gif" alt="foo" />' + end,
     "Test(6) [26] Attribute 'height' is recommended for <img> but not present.\n"
     "Test(6) [26] Attribute 'width' is recommended for <img> but not present.\n"),

    ('Missing title attribute for abbr element',
     start + '<abbr>P4DTI</abbr>' + end,
     "Test(6) [26] Attribute 'title' is recommended for <abbr> but not present.\n"),

    ('Id reference has no target',
     start + '<table><tr><td headers="no-such-header no-sirree"></td></tr></table>' + end,
     "Test(6) [27] IDREF 'no-such-header' has no target.\n"
     "Test(6) [27] IDREF 'no-sirree' has no target.\n"),

    ('Head element has two titles',
     """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head><title>Title one</title><title>Title two</title></head><body /></html>""",
     "Test(4) [15] Contents of <head> element [title, title] doesn't match XHTML specification (%head.misc;, ((title, %head.misc;, (base, %head.misc;)?) | (base, %head.misc;, (title, %head.misc;)))).\n"),

    ('HTML element has two bodies',
     """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head><title>Title one</title></head> <body /> <body /> </html>""",
     "Test(4) [15] Contents of <html> element [head, body, body] doesn't match XHTML specification (head, body).\n"),
    ]

def tests():
    suite = unittest.TestSuite()
    for (name, text, error) in test_cases:
        suite.addTest(case(name, text, error))
    return suite

if __name__ == "__main__":
    unittest.main(defaultTest="tests")


# A. REFERENCES
#
# [PyUnit] "PyUnit - a unit testing framework for Python"; Steve Purcell;
# <http://pyunit.sourceforge.net/>.
#
#
# B. DOCUMENT HISTORY
#
# 2001-05-06 GDR Created.
#
#
# C. COPYRIGHT AND LICENCE
#
# Copyright 2001 Gareth Rees.  This document is provided "as is", without any
# express or implied warranty.  In no event will the author be held liable for
# any damages arising from the use of this document.  You may make and
# distribute verbatim copies of this document provided that you do not charge a
# fee for this document or for its distribution.
#
#
# $Id: //info.ravenbrook.com/project/p4dti/version/1.1/test/test_check_xhtml.py#3 $
