diff --git a/deformdemo/scripts/checkboxchoice.html b/deformdemo/scripts/checkboxchoice.html new file mode 100644 index 00000000..30d17dff --- /dev/null +++ b/deformdemo/scripts/checkboxchoice.html @@ -0,0 +1,262 @@ + + + + + + Deform Demo Site + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+

Checkbox Choice Widget

+
+ +
+ + + + + + + + + +
    +
  • + + + + + + + +
      + + +
    • + +
    • + + + + +
    • + +
    • + + + + +
    • + +
    • + + +
    + + + + + + + + + +
  • +
+ + +
    + +
  • + + + +
  • + +
+ +
+ + + +
+
+

Captured

+
None
+

Code (show in context) +

+
    @view_config(renderer='templates/form.pt', name='checkboxchoice')
+    @demonstrate('Checkbox Choice Widget')
+    def checkboxchoice(self):
+        choices = (('habanero', 'Habanero'),
+                   ('jalapeno', 'Jalapeno'),
+                   ('chipotle', 'Chipotle'))
+
+        class Schema(colander.Schema):
+            pepper = colander.SchemaNode(
+                colander.Set(),
+                widget=deform.widget.CheckboxChoiceWidget(values=choices),
+                validator=colander.Length(min=1),
+                )
+        schema = Schema()
+        form = deform.Form(schema, buttons=('submit',))
+        return self.render_form(form)
+
+ +
+
+ + + + diff --git a/deformdemo/scripts/html5check.py b/deformdemo/scripts/html5check.py new file mode 100644 index 00000000..bd44d5f8 --- /dev/null +++ b/deformdemo/scripts/html5check.py @@ -0,0 +1,192 @@ +#!/usr/bin/python + +# Copyright (c) 2007-2008 Mozilla Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import httplib +import os +import sys +import re +import urlparse +import string +import gzip +import StringIO + +extPat = re.compile(r'^.*\.([A-Za-z]+)$') +extDict = { + "html" : "text/html", + "htm" : "text/html", + "xhtml" : "application/xhtml+xml", + "xht" : "application/xhtml+xml", + "xml" : "application/xml", +} + +argv = sys.argv[1:] + +forceXml = 0 +forceHtml = 0 +gnu = 0 +json = 0 +errorsOnly = 0 +encoding = None +fileName = None +contentType = None +inputHandle = None +service = 'http://html5.validator.nu/' + +for arg in argv: + if '--help' == arg: + print '-h : force text/html' + print '-x : force application/xhtml+xml' + print '-g : GNU output' + print '-j: JSON output' + print '-e : errors only (no info or warnings)' + print '--encoding=foo : declare encoding foo' + print '--service=url : the address of the HTML5 validator' + print 'One file argument allowed. Leave out to read from stdin.' + sys.exit(0) + elif arg.startswith("--encoding="): + encoding = arg[11:] + elif arg.startswith("--service="): + service = arg[10:] + elif arg.startswith("--"): + sys.stderr.write('Unknown argument %s.\n' % arg) + sys.exit(2) + elif arg.startswith("-"): + for c in arg[1:]: + if 'x' == c: + forceXml = 1 + elif 'h' == c: + forceHtml = 1 + elif 'g' == c: + gnu = 1 + elif 'e' == c: + errorsOnly = 1 + elif 'j' == c: + json = 1 + else: + sys.stderr.write('Unknown argument %s.\n' % arg) + sys.exit(3) + else: + if fileName: + sys.stderr.write('Cannot have more than one input file.\n') + sys.exit(1) + fileName = arg + +if forceXml and forceHtml: + sys.stderr.write('Cannot force HTML and XHTML at the same time.\n') + sys.exit(2) + +if forceXml: + contentType = 'application/xhtml+xml' +elif forceHtml: + contentType = 'text/html' +elif fileName: + m = extPat.match(fileName) + if m: + ext = m.group(1) + ext = ext.translate(string.maketrans(string.ascii_uppercase, string.ascii_lowercase)) + if extDict.has_key(ext): + contentType = extDict[ext] + else: + sys.stderr.write('Unable to guess Content-Type from file name. Please force the type.\n') + sys.exit(3) + else: + sys.stderr.write('Could not extract a filename extension. Please force the type.\n') + sys.exit(6) +else: + sys.stderr.write('Need to force HTML or XHTML when reading from stdin.\n') + sys.exit(4) + +if encoding: + contentType = '%s; charset=%s' % (contentType, encoding) + +if fileName: + inputHandle = open(fileName, "rb") +else: + inputHandle = sys.stdin + +data = inputHandle.read() + +buf = StringIO.StringIO() +gzipper = gzip.GzipFile(fileobj=buf, mode='wb') +gzipper.write(data) +gzipper.close() +gzippeddata = buf.getvalue() +buf.close() + +connection = None +response = None +status = 302 +redirectCount = 0 + +url = service +if gnu: + url = url + '?out=gnu' +elif json: + url = url + '?out=json' +else: + url = url + '?out=text' + +if errorsOnly: + url = url + '&level=error' + +while (status == 302 or status == 301 or status == 307) and redirectCount < 10: + if redirectCount > 0: + url = response.getheader('Location') + parsed = urlparse.urlsplit(url) + if parsed[0] != 'http': + sys.stderr.write('URI scheme %s not supported.\n' % parsed[0]) + sys.exit(7) + if redirectCount > 0: + connection.close() # previous connection + print 'Redirecting to %s' % url + print 'Please press enter to continue or type "stop" followed by enter to stop.' + if raw_input() != "": + sys.exit(0) + connection = httplib.HTTPConnection(parsed[1]) + connection.connect() + connection.putrequest("POST", "%s?%s" % (parsed[2], parsed[3]), skip_accept_encoding=1) + connection.putheader("Accept-Encoding", 'gzip') + connection.putheader("Content-Type", contentType) + connection.putheader("Content-Encoding", 'gzip') + connection.putheader("Content-Length", len(gzippeddata)) + connection.endheaders() + connection.send(gzippeddata) + response = connection.getresponse() + status = response.status + redirectCount += 1 + +if status != 200: + sys.stderr.write('%s %s\n' % (status, response.reason)) + sys.exit(5) + +if response.getheader('Content-Encoding', 'identity').lower() == 'gzip': + response = gzip.GzipFile(fileobj=StringIO.StringIO(response.read())) + +if fileName and gnu: + quotedName = '"%s"' % fileName.replace('"', '\\042') + for line in response: + sys.stdout.write(quotedName) + sys.stdout.write(line) +else: + sys.stdout.write(response.read()) + +connection.close() diff --git a/deformdemo/scripts/radiochoice.html b/deformdemo/scripts/radiochoice.html new file mode 100644 index 00000000..fda30c40 --- /dev/null +++ b/deformdemo/scripts/radiochoice.html @@ -0,0 +1,258 @@ + + + + + + Deform Demo Site + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+

Radio Choice Widget

+
+ +
+ + + + + + + + + +
    +
  • + + + + + +
      + + + +
    • + + +
    • + + + + +
    • + + +
    • + + + + +
    • + + +
    • + + + +
    + + + + + + + +
  • +
+ + +
    + +
  • + + + +
  • + +
+ +
+ + + +
+
+

Captured

+
None
+

Code (show in context) +

+
    @view_config(renderer='templates/form.pt', name='radiochoice')
+    @demonstrate('Radio Choice Widget')
+    def radiochoice(self):
+        choices = (('habanero', 'Habanero'), ('jalapeno', 'Jalapeno'),
+                   ('chipotle', 'Chipotle'))
+        class Schema(colander.Schema):
+            pepper = colander.SchemaNode(
+                colander.String(),
+                validator=colander.OneOf([x[0] for x in choices]),
+                widget=deform.widget.RadioChoiceWidget(values=choices),
+                title='Choose your pepper',
+                description='Select a Pepper')
+        schema = Schema()
+        form = deform.Form(schema, buttons=('submit',))
+        return self.render_form(form)
+
+ +
+
+ + + + diff --git a/deformdemo/scripts/sample.json b/deformdemo/scripts/sample.json new file mode 100644 index 00000000..5015772c --- /dev/null +++ b/deformdemo/scripts/sample.json @@ -0,0 +1,33 @@ +{"messages": [ + { + "type": "error", + "lastLine": 158, + "lastColumn": 65, + "firstColumn": 5, + "message": "Element “input” not allowed as child of element “ul” in this context. (Suppressing further errors from this subtree.)", + "extract": "et\"> \n \n \n", + "hiliteStart": 10, + "hiliteLength": 61 + }, + { + "type": "error", + "lastLine": 195, + "lastColumn": 63, + "firstColumn": 5, + "message": "Element “input” not allowed as child of element “ul” in this context. (Suppressing further errors from this subtree.)", + "extract": "\n \n \n", + "hiliteStart": 10, + "hiliteLength": 59 + }, + { + "type": "error", + "lastLine": 152, + "firstLine": 148, + "lastColumn": 10, + "firstColumn": 3, + "message": "The “for” attribute of the “label” element must refer to a form control.", + "extract": "em -->\n\n Choose", + "hiliteStart": 10, + "hiliteLength": 100 + } +]} \ No newline at end of file diff --git a/deformdemo/scripts/validating.rst b/deformdemo/scripts/validating.rst new file mode 100644 index 00000000..6b5a7790 --- /dev/null +++ b/deformdemo/scripts/validating.rst @@ -0,0 +1,35 @@ +Validating HTML pages against the HTML 5 validator +================================================== + + +Why validate? +------------- + +HTML validation is not an end in itself. HTML is necessarily extensible and +HTML 5 has been designed with this in mind. Nevertheless, checking for valid +HTML is a good way to avoid bugs in behaviour between browsers. In addition, +valid HTML is most likely to conform with accessibility requirements as there +are some semantic restrictions, such as which child elements a tag may or not +have, or association, which really make sense when there is only the markup. + + +How to validate? +---------------- + +As there is currently no easily installable library for validation (and I'm +willing to be proved wrong on this). The best way to validate is using an +online service such as that provided by http://validator.nu We have adapted +the Mozilla script for this and added the option to receive results in JSON. +The standalone script can be used with URLs or with local files. Valid pages +always return: {"messages":[]} so an empty `messages` list can be used as a +test pass. + +`sample.json` contains a sample report for a non-validating page. + + +Ignored errors +-------------- + +As noted above, validating HTML is just a means to an end. Some errors will +be detected which cannot fixed and functionality maintained. We have created +a list of such errors. diff --git a/deformdemo/templates/translated_form.pt b/deformdemo/templates/translated_form.pt index 14980cbd..22df9012 100644 --- a/deformdemo/templates/translated_form.pt +++ b/deformdemo/templates/translated_form.pt @@ -1,3 +1,4 @@ +
Up to index diff --git a/deformdemo/test.py b/deformdemo/test.py index f422aa08..01b84aa5 100644 --- a/deformdemo/test.py +++ b/deformdemo/test.py @@ -48,7 +48,7 @@ def test_render_default(self): browser.open(self.url) browser.wait_for_page_to_load("30000") self.assertTrue(browser.is_text_present("Pepper")) - self.assertFalse(browser.is_checked("deformField1-0")) + self.assertFalse(browser.is_checked("deformField1")) self.assertFalse(browser.is_checked("deformField1-1")) self.assertFalse(browser.is_checked("deformField1-2")) self.assertEqual(browser.get_text('css=.req'), '*') @@ -63,7 +63,7 @@ def test_submit_unchecked(self): error_node = 'css=#error-deformField1' self.assertEqual(browser.get_text(error_node), 'Shorter than minimum length 1') - self.assertFalse(browser.is_checked("deformField1-0")) + self.assertFalse(browser.is_checked("deformField1")) self.assertFalse(browser.is_checked("deformField1-1")) self.assertFalse(browser.is_checked("deformField1-2")) self.assertEqual(browser.get_text('css=#captured'), 'None') @@ -71,11 +71,11 @@ def test_submit_unchecked(self): def test_submit_one_checked(self): browser.open(self.url) browser.wait_for_page_to_load("30000") - browser.click("deformField1-0") + browser.click("deformField1") browser.click("submit") browser.wait_for_page_to_load("30000") self.assertFalse(browser.is_element_present('css=.errorMsgLbl')) - self.assertTrue(browser.is_checked("deformField1-0")) + self.assertTrue(browser.is_checked("deformField1")) captured = browser.get_text('css=#captured') self.assertTrue(captured in ( "{'pepper': set([u'habanero'])}", # py2 @@ -85,13 +85,13 @@ def test_submit_one_checked(self): def test_submit_three_checked(self): browser.open(self.url) browser.wait_for_page_to_load("30000") - browser.click("deformField1-0") + browser.click("deformField1") browser.click("deformField1-1") browser.click("deformField1-2") browser.click("submit") browser.wait_for_page_to_load("30000") self.assertFalse(browser.is_element_present('css=.errorMsgLbl')) - self.assertTrue(browser.is_checked("deformField1-0")) + self.assertTrue(browser.is_checked("deformField1")) self.assertTrue(browser.is_checked("deformField1-1")) self.assertTrue(browser.is_checked("deformField1-2")) captured = browser.get_text('css=#captured') @@ -1166,7 +1166,7 @@ def test_render_default(self): browser.open(self.url) browser.wait_for_page_to_load("30000") self.assertTrue(browser.is_text_present("Pepper")) - self.assertFalse(browser.is_checked("deformField1-0")) + self.assertFalse(browser.is_checked("deformField1")) self.assertFalse(browser.is_checked("deformField1-1")) self.assertFalse(browser.is_checked("deformField1-2")) self.assertEqual(browser.get_text('css=.req'), '*') @@ -1180,7 +1180,7 @@ def test_submit_unchecked(self): self.assertTrue(browser.get_text('css=.errorMsgLbl')) error_node = 'css=#error-deformField1' self.assertEqual(browser.get_text(error_node), 'Required') - self.assertFalse(browser.is_checked("deformField1-0")) + self.assertFalse(browser.is_checked("deformField1")) self.assertFalse(browser.is_checked("deformField1-1")) self.assertFalse(browser.is_checked("deformField1-2")) self.assertEqual(browser.get_text('css=#captured'), 'None') @@ -1188,11 +1188,11 @@ def test_submit_unchecked(self): def test_submit_one_checked(self): browser.open(self.url) browser.wait_for_page_to_load("30000") - browser.click("deformField1-0") + browser.click("deformField1") browser.click("submit") browser.wait_for_page_to_load("30000") self.assertFalse(browser.is_element_present('css=.errorMsgLbl')) - self.assertTrue(browser.is_checked("deformField1-0")) + self.assertTrue(browser.is_checked("deformField1")) self.assertSimilarRepr( browser.get_text('css=#captured'), "{'pepper': u'habanero'}") @@ -1202,11 +1202,11 @@ class RadioChoiceWidgetIntTests(RadioChoiceWidgetTests): def test_submit_one_checked(self): browser.open(self.url) browser.wait_for_page_to_load("30000") - browser.click("deformField1-0") + browser.click("deformField1") browser.click("submit") browser.wait_for_page_to_load("30000") self.assertFalse(browser.is_element_present('css=.errorMsgLbl')) - self.assertTrue(browser.is_checked("deformField1-0")) + self.assertTrue(browser.is_checked("deformField1")) self.assertSimilarRepr( browser.get_text('css=#captured'), "{'pepper': 0}") @@ -2855,7 +2855,7 @@ def test_it(self): browser.wait_for_page_to_load("30000") self.assertTrue(browser.is_element_present('css=form > fieldset > ul > li.field.top_level_mapping_widget_custom_class')) self.assertTrue(browser.is_element_present('css=[title=SequenceWidget] > .deformSeq > ul > li.sequenced_widget_custom_class')) - self.assertTrue(browser.is_element_present('css=[title=MappingWidget] > fieldset > ul > li.mapped_widget_custom_class')) + self.assertTrue(browser.is_element_present('css=[title=MappingWidget] > fieldset > ul.mapping > li > ul > li.mapped_widget_custom_class')) if __name__ == '__main__': diff --git a/deformdemo/validation.py b/deformdemo/validation.py index 3ec574ce..9ddd194d 100644 --- a/deformdemo/validation.py +++ b/deformdemo/validation.py @@ -1,6 +1,87 @@ -import unittest from pyramid.paster import bootstrap from deformdemo import DeformDemo +import unittest +import httplib +import sys +import re +import urlparse +import gzip +import StringIO +import json + + +def validate(data): + + extPat = re.compile(r'^.*\.([A-Za-z]+)$') + extDict = { + "html": "text/html", + "htm": "text/html", + "xhtml": "application/xhtml+xml", + "xht": "application/xhtml+xml", + "xml": "application/xml", + } + + + errorsOnly = 0 + encoding = None + contentType = "text/html" + service = 'http://html5.validator.nu/' + + buf = StringIO.StringIO() + gzipper = gzip.GzipFile(fileobj=buf, mode='wb') + gzipper.write(data) + gzipper.close() + gzippeddata = buf.getvalue() + buf.close() + + connection = None + response = None + status = 302 + redirectCount = 0 + + url = service + '?out=json' + url = service + '?out=text' + if errorsOnly: + url = url + '&level=error' + + while (status == 302 or status == 301 or status == 307) and redirectCount < 10: + if redirectCount > 0: + url = response.getheader('Location') + parsed = urlparse.urlsplit(url) + if parsed[0] != 'http': + sys.stderr.write('URI scheme %s not supported.\n' % parsed[0]) + sys.exit(7) + if redirectCount > 0: + connection.close() # previous connection + print 'Redirecting to %s' % url + print 'Please press enter to continue or type "stop" followed by enter to stop.' + if raw_input() != "": + sys.exit(0) + connection = httplib.HTTPConnection(parsed[1]) + connection.connect() + connection.putrequest("POST", "%s?%s" % (parsed[2], parsed[3]), skip_accept_encoding=1) + connection.putheader("Accept-Encoding", 'gzip') + connection.putheader("Content-Type", contentType) + connection.putheader("Content-Encoding", 'gzip') + connection.putheader("Content-Length", len(gzippeddata)) + connection.endheaders() + connection.send(gzippeddata) + response = connection.getresponse() + status = response.status + redirectCount += 1 + + if status != 200: + sys.stderr.write('%s %s\n' % (status, response.reason)) + sys.exit(5) + + if response.getheader('Content-Encoding', 'identity').lower() == 'gzip': + response = gzip.GzipFile(fileobj=StringIO.StringIO(response.read())) + + connection.close() + return response.read() + result = json.loads(response.read()) + return result + class FunctionalTests(unittest.TestCase): @@ -13,11 +94,21 @@ def setUp(self): self.demos = DeformDemo(self.request) def test_valid_html(self): + errors = [] demos_urls = self.demos.get_demos() for demo in demos_urls: res = self.testapp.get(demo[1], status=200) - import pdb; pdb.set_trace() # NOQA - #self.failUnless('Pyramid' in res.body) + check = validate(res.body) + #import pdb; pdb.set_trace() # NOQA + try: + self.assertFalse(check) + print demo[0], "." + except AssertionError, e: + errors.append((demo[0], check)) + print demo[0], "E" + print check + print + #self.assertFalse(errors) if __name__ == '__main__': unittest.main() diff --git a/deformdemo/validation.txt b/deformdemo/validation.txt new file mode 100644 index 00000000..06da5171 --- /dev/null +++ b/deformdemo/validation.txt @@ -0,0 +1,8 @@ +HTML Template Validation Known Issues +===================================== + +Much work was done to make these tempaltes validate, but due to current limitations of HTML and form controls some things just aren't as nice as they should be. Here is the current list of things that just won't validate for now. + +* Extended Attribute usage on HTML elements to pack data into the form +* There is a workaround for mapping items which sets the label of the mapping item for the first item of subfields but this cannot work with sequences which may initially render empty. +* The validator runner is naive and currently reports all feedback from the validation service. This will improve when we switch to JSON reporting and gracefully handle errors that we know we cannot fix.