[Checkins] SVN: zope.app.form/trunk/ * Get ready for release.

Stephan Richter srichter at cosmos.phy.tufts.edu
Wed Oct 24 11:27:12 EDT 2007


Log message for revision 81040:
  * Get ready for release.
  
  * Merge functional tests into generic tests/ folder.
  
  * Made sure the long description will render.
  
  

Changed:
  U   zope.app.form/trunk/CHANGES.txt
  A   zope.app.form/trunk/README.txt
  U   zope.app.form/trunk/buildout.cfg
  U   zope.app.form/trunk/setup.py
  _U  zope.app.form/trunk/src/
  D   zope.app.form/trunk/src/zope/app/form/browser/ftests/
  U   zope.app.form/trunk/src/zope/app/form/browser/i18n.txt
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/i18n.zcml
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/locales/
  U   zope.app.form/trunk/src/zope/app/form/browser/tests/support.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_booleanradiowidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_checkboxwidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_datetimewidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_decimalwidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_editview.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_filewidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_floatwidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_i18n.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_intwidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_objectwidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_selectwidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_textareawidget.py
  A   zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_textwidget.py
  U   zope.app.form/trunk/src/zope/app/form/browser/widgets.txt
  U   zope.app.form/trunk/src/zope/app/form/ftesting.zcml
  U   zope.app.form/trunk/src/zope/app/form/utility.py

-=-
Modified: zope.app.form/trunk/CHANGES.txt
===================================================================
--- zope.app.form/trunk/CHANGES.txt	2007-10-24 15:19:13 UTC (rev 81039)
+++ zope.app.form/trunk/CHANGES.txt	2007-10-24 15:27:12 UTC (rev 81040)
@@ -1,13 +1,15 @@
 =======
-Changes
+CHANGES
 =======
 
-3.4.0
-=====
+3.4.0 (2007-10-24)
+==================
 
- - zope.app.form now supports Python2.5
+- ``zope.app.form`` now supports Python2.5
 
+- Initial release independent of the main Zope tree.
 
+
 Before 3.4
 ==========
 

Added: zope.app.form/trunk/README.txt
===================================================================
--- zope.app.form/trunk/README.txt	                        (rev 0)
+++ zope.app.form/trunk/README.txt	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,3 @@
+This package provides a form and widget framework for Zope 3. It also
+implements a few high-level ZCML directives for declaring forms. More advanced
+alternatives are implemented in ``zope.formlib`` and ``z3c.form``.


Property changes on: zope.app.form/trunk/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: zope.app.form/trunk/buildout.cfg
===================================================================
--- zope.app.form/trunk/buildout.cfg	2007-10-24 15:19:13 UTC (rev 81039)
+++ zope.app.form/trunk/buildout.cfg	2007-10-24 15:27:12 UTC (rev 81040)
@@ -1,9 +1,7 @@
 [buildout]
 develop = .
 parts = test
-find-links = http://download.zope.org/distribution/
 
 [test]
 recipe = zc.recipe.testrunner
-defaults = ['--tests-pattern', '^f?tests$']
 eggs = zope.app.form [test]

Modified: zope.app.form/trunk/setup.py
===================================================================
--- zope.app.form/trunk/setup.py	2007-10-24 15:19:13 UTC (rev 81039)
+++ zope.app.form/trunk/setup.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -1,16 +1,68 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Setup for zope.app.form package
+
+$Id: setup.py 81002 2007-10-24 01:19:47Z srichter $
+"""
+import os
 from setuptools import setup, find_packages
 
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
 setup(name='zope.app.form',
-      version = '3.4.0b3',
-      url='http://svn.zope.org/zope.app.form',
+      version = '3.4.0',
       author='Zope Corporation and Contributors',
       author_email='zope3-dev at zope.org',
-
-      packages=find_packages("src"),
-      package_dir={"": "src"},
-
-      namespace_packages=["zope", "zope.app"],
-      include_package_data=True,
+      description='The Original Zope 3 Form Framework',
+      long_description=(
+          read('README.txt')
+          + '\n\n' +
+          'Detailed Documentation\n'
+          '----------------------\n'
+          + '\n\n' +
+          read('src', 'zope', 'app', 'form', 'browser', 'README.txt')
+          + '\n\n' +
+          read('src', 'zope', 'app', 'form', 'browser', 'widgets.txt')
+          + '\n\n' +
+          read('src', 'zope', 'app', 'form', 'browser', 'objectwidget.txt')
+          + '\n\n' +
+          read('src', 'zope', 'app', 'form', 'browser', 'source.txt')
+          + '\n\n' +
+          read('src', 'zope', 'app', 'form', 'browser', 'i18n.txt')
+          + '\n\n' +
+          read('CHANGES.txt')
+          ),
+      keywords = "zope3 form widget zcml",
+      classifiers = [
+          'Development Status :: 5 - Production/Stable',
+          'Environment :: Web Environment',
+          'Intended Audience :: Developers',
+          'License :: OSI Approved :: Zope Public License',
+          'Programming Language :: Python',
+          'Natural Language :: English',
+          'Operating System :: OS Independent',
+          'Topic :: Internet :: WWW/HTTP',
+          'Framework :: Zope3'],
+      url='http://cheeseshop.python.org/pypi/zope.app.form',
+      license='ZPL 2.1',
+      packages=find_packages('src'),
+      package_dir = {'': 'src'},
+      namespace_packages=['zope', 'zope.app'],
+      extras_require={"test": ['zope.app.testing',
+                               'zope.app.securitypolicy',
+                               'zope.app.zcmlfiles']},
       install_requires=[
           "setuptools",
           "ZODB3",
@@ -28,11 +80,9 @@
           "zope.schema",
           "zope.security",
           "zope.app.basicskin",
-          "zope.location>=3.4.0a1-1",
+          "zope.location",
           ],
-      extras_require={"test": ['zope.app.testing',
-                               'zope.app.securitypolicy',
-                               'zope.app.zcmlfiles']},
-      zip_safe=False,
+      include_package_data = True,
+      zip_safe = False,
       )
 


Property changes on: zope.app.form/trunk/src
___________________________________________________________________
Name: svn:ignore
   + zope.app.form.egg-info


Modified: zope.app.form/trunk/src/zope/app/form/browser/i18n.txt
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/i18n.txt	2007-10-24 15:19:13 UTC (rev 81039)
+++ zope.app.form/trunk/src/zope/app/form/browser/i18n.txt	2007-10-24 15:27:12 UTC (rev 81040)
@@ -1,3 +1,4 @@
+====================
 Internationalization
 ====================
 
@@ -21,27 +22,27 @@
   ... Authorization: Basic mgr:mgrpw
   ... Content-Length: 670
   ... Content-Type: multipart/form-data; boundary=---------------------------19588947601368617292863650127
-  ... 
+  ...
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="field.title"
-  ... 
-  ... 
+  ...
+  ...
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="field.description"
-  ... 
-  ... 
+  ...
+  ...
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="field.somenumber"
-  ... 
+  ...
   ... 0
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ... 
+  ...
   ... Hinzufxgen
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="add_input_name"
-  ... 
-  ... 
+  ...
+  ...
   ... -----------------------------19588947601368617292863650127--
   ... """, handle_errors=False)
   HTTP/1.1 200 OK
@@ -79,27 +80,27 @@
   ... Authorization: Basic mgr:mgrpw
   ... Content-Length: 670
   ... Content-Type: multipart/form-data; boundary=---------------------------19588947601368617292863650127
-  ... 
+  ...
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="field.title"
-  ... 
-  ... 
+  ...
+  ...
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="field.description"
-  ... 
-  ... 
+  ...
+  ...
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="field.somenumber"
-  ... 
+  ...
   ... 0
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="UPDATE_SUBMIT"
-  ... 
+  ...
   ... Hinzufxgen
   ... -----------------------------19588947601368617292863650127
   ... Content-Disposition: form-data; name="add_input_name"
-  ... 
-  ... 
+  ...
+  ...
   ... -----------------------------19588947601368617292863650127--
   ... """, handle_errors=False)
   HTTP/1.1 200 OK

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/i18n.zcml (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/i18n.zcml)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/i18n.zcml	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/i18n.zcml	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,17 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    xmlns:i18n="http://namespaces.zope.org/i18n"
+    i18n_domain="formtest">
+
+  <browser:addform
+     schema=".test_functional_i18n.IFieldContent"
+     content_factory=".test_functional_i18n.FieldContent"
+     name="addfieldcontent.html"
+     label="Add Field Content"
+     permission="zope.Public"
+     />
+
+  <i18n:registerTranslations directory="locales"/>
+
+</configure>

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/locales (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/locales)

Modified: zope.app.form/trunk/src/zope/app/form/browser/tests/support.py
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/support.py	2007-10-24 15:19:13 UTC (rev 81039)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/support.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -15,6 +15,9 @@
 
 $Id$
 """
+import re
+from zope.configuration import xmlconfig
+
 class VerifyResults(object):
     """Mix-in for test classes with helpers for checking string data."""
 
@@ -31,3 +34,88 @@
         for check in check_list:
             self.assert_(result.find(check) < 0,
                          "%r unexpectedly found in %r" % (check, result))
+
+def registerEditForm(schema, widgets={}):
+    """Registers an edit form for the specified schema.
+
+    widgets is a mapping of field name to dict. The dict for each field must
+    contain a 'class' item, which is the widget class, and any additional
+    widget attributes (e.g. text field size, rows, cols, etc.)
+    """
+    widgetsXml = []
+    for field in widgets:
+        widgetsXml.append('<widget field="%s"' % field)
+        for attr in widgets[field]:
+            widgetsXml.append(' %s="%s"' % (attr, widgets[field][attr]))
+        widgetsXml.append(' />')
+    xmlconfig.string("""
+        <configure xmlns="http://namespaces.zope.org/browser">
+          <include package="zope.app.form.browser" file="meta.zcml" />
+          <editform
+            name="edit.html"
+            schema="%s"
+            permission="zope.View">
+            %s
+          </editform>
+        </configure>
+        """ % (schema.__identifier__, ''.join(widgetsXml)))
+
+
+def defineSecurity(class_, schema):
+    class_ = '%s.%s' % (class_.__module__, class_.__name__)
+    schema = schema.__identifier__
+    xmlconfig.string("""
+        <configure xmlns="http://namespaces.zope.org/zope">
+          <include package="zope.app.component" file="meta.zcml" />
+          <class class="%s">
+            <require
+              permission="zope.Public"
+              interface="%s"
+              set_schema="%s" />
+          </class>
+        </configure>
+        """ % (class_, schema, schema))
+
+
+def defineWidgetView(field_interface, widget_class, view_type):
+    field_interface = field_interface.__identifier__
+    widget_class = '%s.%s' % (widget_class.__module__, widget_class.__name__)
+    view_type = '%s.%s' % (view_type.__module__, view_type.__name__)
+    xmlconfig.string("""
+        <configure xmlns="http://namespaces.zope.org/zope">
+          <include package="zope.app.component" file="meta.zcml" />
+          <view
+            for="%s"
+            type="zope.publisher.interfaces.browser.IBrowserRequest"
+            factory="%s"
+            provides="%s"
+            permission="zope.Public"
+            />
+        </configure>
+        """ % (field_interface, widget_class, view_type))
+
+
+def patternExists(pattern, source, flags=0):
+    return re.search(pattern, source, flags) is not None
+
+
+def validationErrorExists(field, error_msg, source):
+    regex = re.compile(r'%s.*?name="field.(\w+)(?:\.[\w\.]+)?"' % (error_msg,),
+                       re.DOTALL)
+    # compile it first because Python 2.3 doesn't allow flags in findall
+    return field in regex.findall(source)
+
+
+def missingInputErrorExists(field, source):
+    return validationErrorExists(field, 'Required input is missing.', source)
+
+
+def invalidValueErrorExists(field, source):
+    # assumes this error is displayed for select elements
+    return patternExists(
+        'Invalid value.*name="field.%s".*</select>' % field,
+        source, re.DOTALL)
+
+
+def updatedMsgExists(source):
+    return patternExists('<p>Updated .*</p>', source)

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_booleanradiowidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_booleanradiowidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_booleanradiowidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_booleanradiowidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,149 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Radio Widget Functional Tests
+
+$Id$
+"""
+import unittest
+import transaction
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.schema import Bool
+from zope.traversing.api import traverse
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+
+class IFoo(Interface):
+
+    bar = Bool(title=u'Bar')
+
+class Foo(Persistent):
+
+    implements(IFoo)
+
+    def __init__(self):
+        self.bar = True
+
+class Test(BrowserTestCase):
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(IFoo, widgets={
+            'bar': { 'class': 'zope.app.form.browser.BooleanRadioWidget' }})
+        defineSecurity(Foo, IFoo)
+
+    def test_display_editform(self):
+        self.getRootFolder()['foo'] = Foo()
+        transaction.commit()
+
+        # display edit view
+        response = self.publish('/foo/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # bar field should be displayed as two radio buttons
+        self.assert_(patternExists(
+            '<input .*checked="checked".*name="field.bar".*type="radio".*'
+            'value="on".* />',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<input .*name="field.bar".*type="radio".*value="off".* />',
+            response.getBody()))
+
+        # a hidden element is used to note that the field is present
+        self.assert_(patternExists(
+            '<input name="field.bar-empty-marker" type="hidden" value="1".* />',
+            response.getBody()))
+
+
+    def test_submit_editform(self):
+        self.getRootFolder()['foo'] = Foo()
+        transaction.commit()
+
+        # submit edit view
+        response = self.publish('/foo/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.bar' : 'off'})
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'foo')
+        self.assertEqual(object.bar, False)
+
+
+    def test_missing_value(self):
+        self.getRootFolder()['foo'] = Foo()
+        transaction.commit()
+
+        # temporarily make bar field not required
+        IFoo['bar'].required = False
+
+        # submit missing value for bar
+        response = self.publish('/foo/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.bar-empty-marker' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # confirm use of missing_value as new object value
+        self.assert_(IFoo['bar'].missing_value is None)
+        object = traverse(self.getRootFolder(), 'foo')
+        self.assert_(object.bar is None)
+
+        # restore bar required state
+        IFoo['bar'].required = True
+
+
+    def test_required_validation(self):
+        self.getRootFolder()['foo'] = Foo()
+        transaction.commit()
+
+        self.assert_(IFoo['bar'].required)
+
+        # submit missing value for required field bar
+        response = self.publish('/foo/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.bar-empty-marker' : '1'})
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm error msgs
+        self.assert_(missingInputErrorExists('bar', response.getBody()))
+
+
+    def test_invalid_allowed_value(self):
+        self.getRootFolder()['foo'] = Foo()
+        transaction.commit()
+
+        # submit a value for bar isn't allowed
+        response = self.publish('/foo/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.bar' : 'bogus' })
+        self.assertEqual(response.getStatus(), 200)
+
+        self.assert_(validationErrorExists('bar', 'Invalid value',
+            response.getBody()))
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_checkboxwidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_checkboxwidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_checkboxwidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_checkboxwidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,150 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Checkbox Widget tests
+
+$Id$
+"""
+import unittest
+import transaction
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.schema import Bool
+from zope.traversing.api import traverse
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser import CheckBoxWidget
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+
+class IBoolTest(Interface):
+
+    b1 = Bool(
+        required=True)
+
+    b2 = Bool(
+        required=False)
+
+class BoolTest(Persistent):
+
+    implements(IBoolTest)
+
+    def __init__(self):
+        self.b1 = True
+        self.b2 = False
+
+class Test(BrowserTestCase):
+
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(IBoolTest)
+        defineSecurity(BoolTest, IBoolTest)
+
+    def test_display_editform(self):
+        self.getRootFolder()['test'] = BoolTest()
+        transaction.commit()
+
+        # display edit view
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # b1 and b2 should be displayed in checkbox input fields
+        self.assert_(patternExists(
+            '<input .* checked="checked".* name="field.b1".* ' \
+            'type="checkbox".* />',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<input .* name="field.b2".* type="checkbox".* />',
+            response.getBody()))
+        # confirm that b2 is *not* checked
+        self.assert_(not patternExists(
+            '<input .* checked="checked".* name="field.b2".* ' \
+            'type="checkbox".* />',
+            response.getBody()))
+
+
+    def test_submit_editform(self):
+        self.getRootFolder()['test'] = BoolTest()
+        transaction.commit()
+
+        # submit edit view
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.b1' : '',
+            'field.b2' : 'on' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.b1, False)
+        self.assertEqual(object.b2, True)
+
+
+    def test_unexpected_value(self):
+        object = BoolTest()
+        object.b1 = True
+        object.b2 = True
+        self.getRootFolder()['test'] = object
+        transaction.commit()
+
+        # submit invalud type for text line
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.b1' : 'true',
+            'field.b2' : 'foo' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # values other than 'on' should be treated as False
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.b1, False)
+        self.assertEqual(object.b2, False)
+
+
+    def test_missing_value(self):
+        # Note: checkbox widget doesn't support a missing value. This
+        # test confirms that one cannot set a Bool field to None.
+
+        self.getRootFolder()['test'] = BoolTest()
+        transaction.commit()
+
+        # confirm default value of b1 is True
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.b1, True)
+
+        # submit missing for b1
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.b1' : CheckBoxWidget._missing })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # confirm b1 is not missing
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.b1 != Bool.missing_value)
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')
+
+

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_datetimewidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_datetimewidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_datetimewidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_datetimewidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,231 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""DateTime Widget Functional Tests
+
+$Id$
+"""
+import unittest
+import re
+import transaction
+from persistent import Persistent
+from datetime import datetime
+
+import zope.security.checker
+from zope.datetime import parseDatetimetz, tzinfo
+from zope.interface import Interface, implements
+from zope.schema import Datetime, Choice
+from zope.traversing.api import traverse
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+
+
+class IDatetimeTest(Interface):
+
+    d2 = Datetime(
+        required=False)
+
+    d3 = Choice(
+        required=False,
+        values=(
+            datetime(2003, 9, 15, tzinfo=tzinfo(0)),
+            datetime(2003, 10, 15, tzinfo=tzinfo(0))),
+        missing_value=datetime(2000, 1, 1, tzinfo=tzinfo(0)))
+
+    d1 = Datetime(
+        required=True,
+        min=datetime(2003, 1, 1, tzinfo=tzinfo(0)),
+        max=datetime(2020, 12, 31, tzinfo=tzinfo(0)))
+
+class DatetimeTest(Persistent):
+
+    implements(IDatetimeTest)
+
+    def __init__(self):
+        self.d1 = datetime(2003, 4, 6, tzinfo=tzinfo(0))
+        self.d2 = datetime(2003, 8, 6, tzinfo=tzinfo(0))
+        self.d3 = None
+
+class Test(BrowserTestCase):
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(IDatetimeTest)
+        defineSecurity(DatetimeTest, IDatetimeTest)
+
+    def getDateForField(self, field, source):
+        """Returns a datetime object for the specified field in source.
+
+        Returns None if the field value cannot be converted to date.
+        """
+
+        # look in input element first
+        pattern = '<input .* name="field.%s".* value="(.*)".*>' % field
+        m = re.search(pattern, source)
+        if m is None:
+            # look in a select element
+            pattern = '<select .* name="field.%s".*>.*' \
+                '<option value="(.*)" selected>*.</select>' % field
+            m = re.search(pattern, source, re.DOTALL)
+            if m is None:
+                return None
+        return parseDatetimetz(m.group(1))
+
+    def test_display_editform(self):
+        self.getRootFolder()['test'] = DatetimeTest()
+        transaction.commit()
+        object = traverse(self.getRootFolder(), 'test')
+
+        # display edit view
+        response = self.publish('/test/edit.html',
+            env={"HTTP_ACCEPT_LANGUAGE": "ru"})
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm date values in form with actual values
+        self.assertEqual(self.getDateForField('d1', response.getBody()),
+            object.d1)
+        self.assertEqual(self.getDateForField('d2', response.getBody()),
+            object.d2)
+        self.assert_(self.getDateForField('d3', response.getBody()) is None)
+
+
+    def test_submit_editform(self):
+        self.getRootFolder()['test'] = DatetimeTest()
+        transaction.commit()
+
+        # submit edit view
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.d1' : u'2003-02-01 00:00:00+00:00',
+            'field.d2' : u'2003-02-02 00:00:00+00:00',
+            'field.d3' : u'2003-10-15 00:00:00+00:00' },
+            env={"HTTP_ACCEPT_LANGUAGE": "en"})
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+
+        self.assertEqual(object.d1, datetime(2003, 2, 1, tzinfo=tzinfo(0)))
+        self.assertEqual(object.d2, datetime(2003, 2, 2, tzinfo=tzinfo(0)))
+        self.assertEqual(object.d3, datetime(2003, 10, 15, tzinfo=tzinfo(0)))
+
+
+    def test_missing_value(self):
+        self.getRootFolder()['test'] = DatetimeTest()
+        transaction.commit()
+
+        # submit missing values for d2 and d3
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.d2' : '',
+            'field.d3-empty-marker' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.d2 is None) # default missing_value for dates
+        # 2000-1-1 is missing_value for d3
+        self.assertEqual(object.d3, datetime(2000, 1, 1, tzinfo=tzinfo(0)))
+
+
+    def test_required_validation(self):
+        self.getRootFolder()['test'] = DatetimeTest()
+        transaction.commit()
+
+        # submit missing values for required field d1
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.d1' : '',
+            'field.d2' : '',
+            'field.d3' : '' })
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm error msgs
+        self.assert_(missingInputErrorExists('d1', response.getBody()))
+        self.assert_(not missingInputErrorExists('d2', response.getBody()))
+        self.assert_(not missingInputErrorExists('d3', response.getBody()))
+
+
+    def test_invalid_value(self):
+        self.getRootFolder()['test'] = DatetimeTest()
+        transaction.commit()
+
+        # submit a value for d3 that isn't allowed
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.d3' : u'2003-02-01 12:00:00+00:00'})
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(invalidValueErrorExists('d3', response.getBody()))
+
+
+    def test_min_max_validation(self):
+        self.getRootFolder()['test'] = DatetimeTest()
+        transaction.commit()
+
+        # submit value for d1 that is too low
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.d1' : u'2002-12-31 12:00:00+00:00'},
+            env={"HTTP_ACCEPT_LANGUAGE": "en"})
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('d1', 'Value is too small',
+            response.getBody()))
+
+        # submit value for i1 that is too high
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.d1' : u'2021-12-01 12:00:00+00:00'},
+            env={"HTTP_ACCEPT_LANGUAGE": "en"})
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('d1', 'Value is too big',
+            response.getBody()))
+
+
+    def test_omitted_value(self):
+        self.getRootFolder()['test'] = DatetimeTest()
+        transaction.commit()
+
+        # remember default values
+        object = traverse(self.getRootFolder(), 'test')
+        d1 = object.d1
+        d2 = object.d2
+        self.assert_(d2 is not None)
+        d3 = object.d3
+
+        # submit change with only d2 present -- note that required
+        # field d1 is omitted, which should not cause a validation error
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.d2' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new value in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.d1, d1)
+        self.assert_(object.d2 is None)
+        self.assertEqual(object.d3, d3)
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_decimalwidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_decimalwidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_decimalwidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_decimalwidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,235 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002, 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Decimal Widget Functional Tests
+
+$Id$
+"""
+import unittest
+import decimal
+import transaction
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.traversing.api import traverse
+from zope.schema import Decimal, Choice
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+
+class IDecimalTest(Interface):
+
+    f1 = Decimal(
+        required=False,
+        min=decimal.Decimal("1.1"),
+        max=decimal.Decimal("10.1"))
+
+    f2 = Decimal(
+        required=False)
+
+    f3 = Choice(
+        required=True,
+        values=(decimal.Decimal("0.0"), decimal.Decimal("1.1"),
+                decimal.Decimal("2.1"), decimal.Decimal("3.1"),
+                decimal.Decimal("5.1"), decimal.Decimal("7.1"),
+                decimal.Decimal("11.1")),
+        missing_value=0)
+
+    f4 = Decimal(readonly=True)
+
+
+class DecimalTest(Persistent):
+
+    implements(IDecimalTest)
+
+    def __init__(self):
+        self.f1 = None
+        self.f2 = decimal.Decimal("1.1")
+        self.f3 = decimal.Decimal("2.1")
+        self.f4 = decimal.Decimal("17.2")
+
+
+class Test(BrowserTestCase):
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(IDecimalTest)
+        defineSecurity(DecimalTest, IDecimalTest)
+
+    def test_display_editform(self):
+        self.getRootFolder()['test'] = DecimalTest()
+        transaction.commit()
+
+        # display edit view
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # f1 and f2 should be displayed in text fields
+        self.assert_(patternExists(
+            '<input .* name="field.f1".* value="".*>', response.getBody()))
+        self.assert_(patternExists(
+            '<input .* name="field.f2".* value="1.1".*>', response.getBody()))
+
+        # f3 should be in a dropdown
+        self.assert_(patternExists(
+            '<select .*name="field.f3".*>', response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="2.1">2.1</option>',
+            response.getBody()))
+
+        # f4 should be rendered by the display widget
+        self.assert_(patternExists(
+            '<div class="field">17.2</div>', response.getBody()))
+
+
+    def test_submit_editform(self):
+        self.getRootFolder()['test'] = DecimalTest()
+        transaction.commit()
+
+        # submit edit view
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '1.123',
+            'field.f2' : '2.23456789012345',
+            'field.f3' : '11.1' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.f1, decimal.Decimal("1.123"))
+        self.assertEqual(object.f2, decimal.Decimal("2.23456789012345"))
+        self.assertEqual(object.f3, decimal.Decimal("11.1"))
+
+
+    def test_missing_value(self):
+        self.getRootFolder()['test'] = DecimalTest()
+        transaction.commit()
+
+        # submit missing values for f2 and f3
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '',
+            'field.f2' : '',
+            'field.f3' : '1.1' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.f1, None)
+        self.assertEqual(object.f2, None) # None is default missing_value
+        self.assertEqual(object.f3, decimal.Decimal("1.1"))  # 0 is from f3.missing_value=0
+
+
+    def test_required_validation(self):
+        self.getRootFolder()['test'] = DecimalTest()
+        transaction.commit()
+
+        # submit missing values for required field f1
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '',
+            'field.f2' : '',
+            'field.f3' : '' })
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm error msgs
+        self.assert_(not missingInputErrorExists('f1', response.getBody()))
+        self.assert_(not missingInputErrorExists('f2', response.getBody()))
+        self.assert_(missingInputErrorExists('f3', response.getBody()))
+
+
+    def test_invalid_allowed_value(self):
+        self.getRootFolder()['test'] = DecimalTest()
+        transaction.commit()
+
+        # submit a value for f3 that isn't allowed
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f3' : '10000' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(invalidValueErrorExists('f3', response.getBody()))
+
+
+    def test_min_max_validation(self):
+        self.getRootFolder()['test'] = DecimalTest()
+        transaction.commit()
+
+        # submit value for f1 that is too low
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '-1' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('f1', 'Value is too small',
+            response.getBody()))
+
+        # submit value for f1 that is too high
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '1000.2' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('f1', 'Value is too big',
+            response.getBody()))
+
+
+    def test_omitted_value(self):
+        self.getRootFolder()['test'] = DecimalTest()
+        transaction.commit()
+
+        # confirm default values
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.f1 is None)
+        self.assertEqual(object.f2, decimal.Decimal("1.1"))
+        self.assertEqual(object.f3, decimal.Decimal("2.1"))
+
+        # submit change with only f2 present -- note that required
+        # field f1 is omitted, which should not cause a validation error
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f2' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new value in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.f1 is None)
+        self.assert_(object.f2 is None)
+        self.assertEqual(object.f3, decimal.Decimal("2.1"))
+
+
+    def test_conversion(self):
+        self.getRootFolder()['test'] = DecimalTest()
+        transaction.commit()
+
+        # submit value for f1 that cannot be convert to an float
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : 'foo' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists(
+            'f1',
+            'Invalid decimal data', response.getBody()))
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')
+

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_editview.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_editview.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_editview.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_editview.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,98 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Editview tests
+
+$Id$
+"""
+import unittest
+import transaction
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.schema import TextLine
+from zope.traversing.api import traverse
+
+from zope.app.form.browser.editview import EditView
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+
+class IFoo(Interface):
+
+    optional_text = TextLine(required=False)
+    required_text = TextLine(required=True)
+
+class Foo(Persistent):
+
+    implements(IFoo)
+
+class Test(BrowserTestCase):
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(IFoo)
+        defineSecurity(Foo, IFoo)
+
+    def test_rollback_on_error(self):
+        """Tests rollback when a widget error occurs.
+
+        When one or more errors are generated by input widgets, the current
+        transaction should be rolledback to ensure object integrity.
+        """
+        self.getRootFolder()['foo'] = Foo()
+        self.getRootFolder()['foo'].required_text = u'initial required'
+        self.getRootFolder()['foo'].optional_text = u'initial optional'
+        transaction.commit()
+
+        # submit form with legal value for optional_text and invalid for
+        # required_text
+        old_update = EditView.update
+        try:
+            def new_update(self):
+                # This update changes something after form validation has failed.
+                # Side effects like this should not be committed.
+                # http://www.zope.org/Collectors/Zope3-dev/655
+                result = old_update(self)
+                self.context.required_text = u'changed after form validation'
+                return result
+            EditView.update = new_update
+            response = self.publish('/foo/edit.html', form={
+                'field.optional_text': u'',
+                'field.required_text': u'',
+                'UPDATE_SUBMIT': ''})
+            self.assertEqual(response.getStatus(), 200)
+        finally:
+            EditView.update = old_update
+
+        # confirm that one errors exists
+        self.assert_(patternExists(
+            'There are <strong>1</strong> input errors.', response.getBody()))
+
+        # confirm that foo was not modified
+        foo = traverse(self.getRootFolder(), 'foo')
+        self.assertEquals(foo.required_text, u'initial required')
+        self.assertEquals(foo.optional_text, u'initial optional')
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')
+
+

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_filewidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_filewidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_filewidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_filewidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,176 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""File Widget Tests
+
+$Id$
+"""
+import unittest
+import transaction
+from StringIO import StringIO
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.schema.interfaces import IField
+from zope.schema import Field
+from zope.traversing.api import traverse
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser.textwidgets import FileWidget
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+from zope.app.form.interfaces import IInputWidget
+
+class IFileField(IField):
+    """Field for representing a file that can be edited by FileWidget."""
+
+
+class FileField(Field):
+
+    implements(IFileField)
+
+class IFileTest(Interface):
+
+    f1 = FileField(required=True)
+    f2 = FileField(required=False)
+
+class FileTest(Persistent):
+
+    implements(IFileTest)
+
+    def __init__(self):
+        self.f1 = None
+        self.f2 = 'foo'
+
+
+class SampleTextFile(StringIO):
+
+    def __init__(self, buf, filename=''):
+        StringIO.__init__(self, buf)
+        self.filename = filename
+
+
+class Test(BrowserTestCase):
+
+    sampleText = "The quick brown fox\njumped over the lazy dog."
+    sampleTextFile = SampleTextFile(sampleText)
+
+    emptyFileName = 'empty.txt'
+    emptyFile = SampleTextFile('', emptyFileName)
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        defineWidgetView(IFileField, FileWidget, IInputWidget)
+        registerEditForm(IFileTest)
+        defineSecurity(FileTest, IFileTest)
+
+    def test_display_editform(self):
+        self.getRootFolder()['test'] = FileTest()
+        transaction.commit()
+
+        # display edit view
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # field should be displayed in a file input element
+        self.assert_(patternExists(
+            '<input .* name="field.f1".* type="file".*>', response.getBody()))
+        self.assert_(patternExists(
+            '<input .* name="field.f2".* type="file".*>', response.getBody()))
+
+
+    def test_submit_text(self):
+        self.getRootFolder()['test'] = FileTest()
+        transaction.commit()
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.f1 is None)
+        self.assertEqual(object.f2, 'foo')
+
+        # submit a sample text file
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : self.sampleTextFile,
+            'field.f2' : self.sampleTextFile,
+            'field.f1.used' : '',
+            'field.f2.used' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.f1, self.sampleText)
+        self.assertEqual(object.f2, self.sampleText)
+
+
+    def test_invalid_value(self):
+        self.getRootFolder()['test'] = FileTest()
+        transaction.commit()
+
+        # submit an invalid file value
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : 'not a file - same as missing input',
+            'field.f1.used' : '',
+            'field.f2.used' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('f1',
+            'Form input is not a file object', response.getBody()))
+
+
+    def test_required_validation(self):
+        self.getRootFolder()['test'] = FileTest()
+        transaction.commit()
+
+        # submit missing value for required field f1
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1.used' : '',
+            'field.f2.used' : ''})
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm error msgs
+        self.assert_(missingInputErrorExists('f1', response.getBody()))
+        self.assert_(not missingInputErrorExists('f2', response.getBody()))
+
+
+    def test_empty_file(self):
+        self.getRootFolder()['test'] = FileTest()
+        transaction.commit()
+
+        # submit an empty text file
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f2' : self.emptyFile,
+            # 'field.f1.used' : '', # we don't let f1 know that it was rendered
+            # or else it will complain (see test_required_validation) and the
+            # change will not succeed.
+            'field.f2.used' : ''})
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # new value for f1 should be field.missing_value (i.e, None)
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.f1 is None)
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')
+
+

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_floatwidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_floatwidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_floatwidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_floatwidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,224 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Float Widget Functional Tests
+
+$Id$
+"""
+import unittest
+import transaction
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.traversing.api import traverse
+from zope.schema import Float, Choice
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+
+class IFloatTest(Interface):
+
+    f1 = Float(
+        required=False,
+        min=1.1,
+        max=10.1)
+
+    f2 = Float(
+        required=False)
+
+    f3 = Choice(
+        required=True,
+        values=(0.0, 1.1, 2.1, 3.1, 5.1, 7.1, 11.1),
+        missing_value=0)
+
+
+class FloatTest(Persistent):
+
+    implements(IFloatTest)
+
+    def __init__(self):
+        self.f1 = None
+        self.f2 = 1.1
+        self.f3 = 2.1
+
+
+class Test(BrowserTestCase):
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(IFloatTest)
+        defineSecurity(FloatTest, IFloatTest)
+
+    def test_display_editform(self):
+        self.getRootFolder()['test'] = FloatTest()
+        transaction.commit()
+
+        # display edit view
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # f1 and f2 should be displayed in text fields
+        self.assert_(patternExists(
+            '<input .* name="field.f1".* value="".*>', response.getBody()))
+        self.assert_(patternExists(
+            '<input .* name="field.f2".* value="1.1".*>', response.getBody()))
+
+        # f3 should be in a dropdown
+        self.assert_(patternExists(
+            '<select .*name="field.f3".*>', response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="2.1">2.1</option>',
+            response.getBody()))
+
+
+    def test_submit_editform(self):
+        self.getRootFolder()['test'] = FloatTest()
+        transaction.commit()
+
+        # submit edit view
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '1.123',
+            'field.f2' : '2.23456789012345',
+            'field.f3' : '11.1' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.f1, 1.123)
+        self.assertEqual(object.f2, 2.23456789012345)
+        self.assertEqual(object.f3, 11.1)
+
+
+    def test_missing_value(self):
+        self.getRootFolder()['test'] = FloatTest()
+        transaction.commit()
+
+        # submit missing values for f2 and f3
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '',
+            'field.f2' : '',
+            'field.f3' : '1.1' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.f1, None)
+        self.assertEqual(object.f2, None) # None is default missing_value
+        self.assertEqual(object.f3, 1.1)  # 0 is from f3.missing_value=0
+
+
+    def test_required_validation(self):
+        self.getRootFolder()['test'] = FloatTest()
+        transaction.commit()
+
+        # submit missing values for required field f1
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '',
+            'field.f2' : '',
+            'field.f3' : '' })
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm error msgs
+        self.assert_(not missingInputErrorExists('f1', response.getBody()))
+        self.assert_(not missingInputErrorExists('f2', response.getBody()))
+        self.assert_(missingInputErrorExists('f3', response.getBody()))
+
+
+    def test_invalid_allowed_value(self):
+        self.getRootFolder()['test'] = FloatTest()
+        transaction.commit()
+
+        # submit a value for f3 that isn't allowed
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f3' : '10000' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(invalidValueErrorExists('f3', response.getBody()))
+
+
+    def test_min_max_validation(self):
+        self.getRootFolder()['test'] = FloatTest()
+        transaction.commit()
+
+        # submit value for f1 that is too low
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '-1' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('f1', 'Value is too small',
+            response.getBody()))
+
+        # submit value for f1 that is too high
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : '1000.2' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('f1', 'Value is too big',
+            response.getBody()))
+
+
+    def test_omitted_value(self):
+        self.getRootFolder()['test'] = FloatTest()
+        transaction.commit()
+
+        # confirm default values
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.f1 is None)
+        self.assertEqual(object.f2, 1.1)
+        self.assertEqual(object.f3, 2.1)
+
+        # submit change with only f2 present -- note that required
+        # field f1 is omitted, which should not cause a validation error
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f2' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new value in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.f1 is None)
+        self.assert_(object.f2 is None)
+        self.assertEqual(object.f3, 2.1)
+
+
+    def test_conversion(self):
+        self.getRootFolder()['test'] = FloatTest()
+        transaction.commit()
+
+        # submit value for f1 that cannot be convert to an float
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.f1' : 'foo' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists(
+            'f1',
+            'Invalid floating point data', response.getBody()))
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')
+

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_i18n.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_i18n.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_i18n.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_i18n.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,84 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test form i18n
+
+$Id$
+"""
+
+import re
+import unittest
+from persistent import Persistent
+from zope.testing import renormalizing, doctest
+from zope.interface import Interface, implements
+from zope.schema import TextLine, Text, Int, List
+from zope.i18nmessageid import MessageFactory
+from zope.app.testing.functional import FunctionalDocFileSuite
+from zope.app.form.testing import AppFormLayer
+
+
+_ = MessageFactory('formtest')
+
+__docformat__ = "reStructuredText"
+
+
+class IFieldContent(Interface):
+
+    title = TextLine(
+        title=_(u"Title"),
+        description=_(u"A short description of the event."),
+        default=u"",
+        required=True
+        )
+
+    description = Text(
+        title=_(u"Description"),
+        description=_(u"A long description of the event."),
+        default=u"",
+        required=False
+        )
+
+    somenumber = Int(
+        title=_(u"Some number"),
+        default=0,
+        required=False
+        )
+
+    somelist = List(
+        title=_(u"Some List"),
+        value_type=TextLine(title=_(u"Some item")),
+        default=[],
+        required=False
+        )
+
+
+class FieldContent(Persistent):
+    implements(IFieldContent)
+
+
+checker = renormalizing.RENormalizing([
+    (re.compile(r"HTTP/1\.1 200 .*"), "HTTP/1.1 200 OK"),
+    ])
+
+
+def test_suite():
+    i18n = FunctionalDocFileSuite('i18n.txt', package='zope.app.form.browser',
+        checker=checker)
+    i18n.layer = AppFormLayer
+    return unittest.TestSuite([
+        i18n,
+        ])
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_intwidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_intwidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_intwidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_intwidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,276 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Int Widget Functional Tests
+
+$Id$
+"""
+import unittest
+import transaction
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.schema import Int, Choice
+from zope.traversing.api import traverse
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.testing.functional import BrowserTestCase
+from zope.app.form.browser.tests.support import *
+
+class IIntTest(Interface):
+
+    i2 = Int(
+        required=False)
+
+    i3 = Choice(
+        required=False,
+        values=(0, 1, 2, 3, 5, 7, 11),
+        missing_value=0)
+
+    i1 = Int(
+        required=True,
+        min=1,
+        max=10)
+
+
+class IIntTest2(Interface):
+    """Used to test an unusual care where missing_value is -1 and
+    not in allowed_values."""
+
+    i1 = Choice(
+        required=False,
+        missing_value=-1,
+        values=(10, 20, 30))
+
+
+class IntTest(Persistent):
+
+    implements(IIntTest)
+
+    def __init__(self):
+        self.i1 = None
+        self.i2 = 1
+        self.i3 = 2
+
+
+class IntTest2(Persistent):
+
+    implements(IIntTest2)
+
+    def __init__(self):
+        self.i1 = 10
+
+
+class Test(BrowserTestCase):
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(IIntTest)
+        registerEditForm(IIntTest2)
+        defineSecurity(IntTest, IIntTest)
+        defineSecurity(IntTest2, IIntTest2)
+
+    def test_display_editform(self):
+        self.getRootFolder()['test'] = IntTest()
+        transaction.commit()
+
+        # display edit view
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # i1 and i2 should be displayed in text fields
+        self.assert_(patternExists(
+            '<input .* name="field.i1".* value="".*>', response.getBody()))
+        self.assert_(patternExists(
+            '<input .* name="field.i2".* value="1".*>', response.getBody()))
+
+        # i3 should be in a dropdown
+        self.assert_(patternExists(
+            '<select .*name="field.i3".*>', response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="2">2</option>',
+            response.getBody()))
+
+
+    def test_submit_editform(self):
+        self.getRootFolder()['test'] = IntTest()
+        transaction.commit()
+
+        # submit edit view
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.i1' : '1',
+            'field.i2' : '2',
+            'field.i3' : '3' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.i1, 1)
+        self.assertEqual(object.i2, 2)
+        self.assertEqual(object.i3, 3)
+
+
+    def test_missing_value(self):
+        self.getRootFolder()['test'] = IntTest()
+        transaction.commit()
+
+        # submit missing values for i2 and i3
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.i1' : '1',
+            'field.i2' : '',
+            'field.i3-empty-marker' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.i1, 1)
+        self.assertEqual(object.i2, None) # None is default missing_value
+        self.assertEqual(object.i3, 0)  # 0 is from i3.missing_value=0
+
+
+    def test_alternative_missing_value(self):
+        """Tests the addition of an empty value at the top of the dropdown
+        that, when selected, updates the field with field.missing_value.
+        """
+
+        self.getRootFolder()['test'] = IntTest2() # note alt. class
+        transaction.commit()
+
+        # display edit form
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm that i1 is has a blank item at top with value=""
+        self.assert_(patternExists(
+            '<select id="field.i1" name="field.i1" .*>', response.getBody()))
+        self.assert_(patternExists(
+            '<option value="">.*</option>', response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="10">10</option>',
+            response.getBody()))
+
+        # submit form as if top item is selected
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.i1-empty-marker' : '1'})
+
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # confirm new value is -1 -- i1.missing_value
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.i1, -1)
+
+
+    def test_required_validation(self):
+        self.getRootFolder()['test'] = IntTest()
+        transaction.commit()
+
+        # submit missing values for required field i1
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.i1' : '',
+            'field.i2' : '',
+            'field.i3' : '' })
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm error msgs
+        self.assert_(missingInputErrorExists('i1', response.getBody()))
+        self.assert_(not missingInputErrorExists('i2', response.getBody()))
+        self.assert_(not missingInputErrorExists('i3', response.getBody()))
+
+
+    def test_invalid_allowed_value(self):
+        self.getRootFolder()['test'] = IntTest()
+        transaction.commit()
+
+        # submit a value for i3 that isn't allowed
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.i3' : '12' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(invalidValueErrorExists('i3', response.getBody()))
+
+
+    def test_min_max_validation(self):
+        self.getRootFolder()['test'] = IntTest()
+        transaction.commit()
+
+        # submit value for i1 that is too low
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.i1' : '-1' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('i1', 'Value is too small',
+            response.getBody()))
+
+        # submit value for i1 that is too high
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.i1' : '11' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('i1', 'Value is too big',
+            response.getBody()))
+
+
+    def test_omitted_value(self):
+        self.getRootFolder()['test'] = IntTest()
+        transaction.commit()
+
+        # confirm default values
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.i1 is None)
+        self.assertEqual(object.i2, 1)
+        self.assertEqual(object.i3, 2)
+
+        # submit change with only i2 present -- note that required
+        # field i1 is omitted, which should not cause a validation error
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.i2' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new value in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assert_(object.i1 is None)
+        self.assert_(object.i2 is None)
+        self.assertEqual(object.i3, 2)
+
+
+    def test_conversion(self):
+        self.getRootFolder()['test'] = IntTest()
+        transaction.commit()
+
+        # submit value for i1 that cannot be convert to an int
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.i1' : 'foo' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('i1', 'Invalid integer data',
+                                           response.getBody()))
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_objectwidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_objectwidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_objectwidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_objectwidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,79 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test object widget
+
+$Id$
+"""
+import unittest
+from zope.testing import doctest
+from zope.interface import Interface, implements
+from zope.publisher.browser import TestRequest
+from zope.schema import Object, TextLine
+import zope.security.checker
+from zope.app.form.browser import ObjectWidget
+from zope.app.testing.functional import BrowserTestCase
+from zope.app.form.browser.tests import support
+from zope.app.form.testing import AppFormLayer
+
+class ITestContact(Interface):
+    name = TextLine()
+    email = TextLine()
+    
+class TestContact(object):
+    implements(ITestContact)
+
+class Test(BrowserTestCase, support.VerifyResults):
+    
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        self.field = Object(ITestContact, __name__=u'foo')
+
+    def test_new(self):
+        request = TestRequest()
+        widget = ObjectWidget(self.field, request, TestContact)
+        self.assertEquals(int(widget.hasInput()), 0)
+        check_list = (
+            'input', 'name="field.foo.name"',
+            'input', 'name="field.foo.email"'
+        )
+        self.verifyResult(widget(), check_list)
+
+    def test_edit(self):
+        request = TestRequest(form={
+            'field.foo.name': u'fred',
+            'field.foo.email': u'fred at fred.com'
+            })
+        widget = ObjectWidget(self.field, request, TestContact)
+        self.assertEquals(int(widget.hasInput()), 1)
+        o = widget.getInputValue()
+        self.assertEquals(hasattr(o, 'name'), 1)
+        self.assertEquals(o.name, u'fred')
+        self.assertEquals(o.email, u'fred at fred.com')
+        check_list = (
+            'input', 'name="field.foo.name"', 'value="fred"',
+            'input', 'name="field.foo.email"', 'value="fred at fred.com"',
+        )
+        self.verifyResult(widget(), check_list)
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')
+
+
+

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_selectwidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_selectwidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_selectwidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_selectwidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,147 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""RadioWidget Tests
+
+$Id$
+"""
+import unittest
+import transaction
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.schema import TextLine, Choice
+from zope.traversing.api import traverse
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+
+class IRadioTest(Interface):
+
+    s3 = Choice(
+        required=False,
+        values=(u'Bob', u'is', u'Your', u'Uncle'))
+
+    s4 = Choice(
+        required=True,
+        values=(u'1', u'2', u'3'))
+
+class RadioTest(Persistent):
+
+    implements(IRadioTest)
+
+    def __init__(self):
+        self.s3 = None
+        self.s4 = u'1'
+
+class Test(BrowserTestCase):
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(IRadioTest)
+        defineSecurity(RadioTest, IRadioTest)
+
+    def test_display_editform(self):
+        self.getRootFolder()['test'] = RadioTest()
+        transaction.commit()
+
+        test = self.getRootFolder()['test']
+        test.s3 = u"Bob"
+
+        # display edit view
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # S3
+        self.assert_(patternExists(
+            '<select .* name="field.s3".*>',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="">',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option value="Bob">',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option value="is">',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option value="Your">',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option value="Uncle">',
+            response.getBody()))
+
+        # S4
+        joined_body = "".join(response.getBody().split("\n"))
+        self.failIf(patternExists(
+            '<select.*name="field.s4".*>.*<option.*value="".*>',
+            joined_body))
+        self.assert_(patternExists(
+            '<select .* name="field.s4".*>',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="1">',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option value="2">',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option value="3">',
+            response.getBody()))
+
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT': '',
+            'field.s3': u'Bob',
+            'field.s4': u'2'})
+        self.assert_(patternExists(
+            '<option selected="selected" value="Bob">',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="2">',
+            response.getBody()))
+
+        response = self.publish('/test/edit.html')
+        self.assert_(patternExists(
+            '<option selected="selected" value="Bob">',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="2">',
+            response.getBody()))
+
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT': '',
+            'field.s3': u''})
+        self.assert_(patternExists(
+            '<option selected="selected" value="">',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="2">',
+            response.getBody()))
+
+        response = self.publish('/test/edit.html')
+        self.assert_(patternExists(
+            '<option selected="selected" value="">',
+            response.getBody()))
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')
+

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_textareawidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_textareawidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_textareawidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_textareawidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,233 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""TextArea Functional Tests
+
+$Id$
+"""
+import unittest
+import transaction
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.schema import Text
+from zope.traversing.api import traverse
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+
+class ITextTest(Interface):
+
+    s2 = Text(
+        required=False,
+        missing_value=u'')
+
+    s3 = Text(
+        required=False)
+
+    s1 = Text(
+        required=True,
+        min_length=2,
+        max_length=10)
+
+
+class TextTest(Persistent):
+
+    implements(ITextTest)
+
+    def __init__(self):
+        self.s1 = ''
+        self.s2 = u'foo'
+        self.s3 = None
+
+
+class Test(BrowserTestCase):
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(ITextTest)
+        defineSecurity(TextTest, ITextTest)
+
+    def test_display_editform(self):
+        self.getRootFolder()['test'] = TextTest()
+        transaction.commit()
+
+        # display edit view
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # all fields should be displayed in text fields
+        self.assert_(patternExists(
+            '<textarea .* name="field.s1".*></textarea>',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<textarea .* name="field.s2".*>foo</textarea>',
+            response.getBody()))
+        self.assert_(patternExists(
+            '<textarea .* name="field.s3".*></textarea>',
+            response.getBody()))
+
+
+    def test_submit_editform(self):
+        self.getRootFolder()['test'] = TextTest()
+        transaction.commit()
+
+        # submit edit view
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : u'foo',
+            'field.s2' : u'bar',
+            'field.s3' : u'baz' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.s1, u'foo')
+        self.assertEqual(object.s2, u'bar')
+        self.assertEqual(object.s3, u'baz')
+
+
+    def test_invalid_type(self):
+        """Tests textarea widget's handling of invalid unicode input.
+
+        The text widget will succeed in converting any form input into
+        unicode.
+        """
+        self.getRootFolder()['test'] = TextTest()
+        transaction.commit()
+
+        # submit invalid type for text
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : 123 }) # not unicode
+        self.assertEqual(response.getStatus(), 200)
+        # Note: We don't have a invalid field value
+        # since we convert the value to unicode
+        self.assert_(not validationErrorExists(
+            's1', 'Object is of wrong type.', response.getBody()))
+
+
+    def test_missing_value(self):
+        self.getRootFolder()['test'] = TextTest()
+        transaction.commit()
+
+        # submit missing values for s2 and s3
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : u'foo',
+            'field.s2' : '',
+            'field.s3' : '' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new value in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.s1, u'foo')
+        self.assertEqual(object.s2, u'')   # default missing_value
+        self.assertEqual(object.s3, None)  # None is s3's missing_value
+
+
+    def test_required_validation(self):
+        self.getRootFolder()['test'] = TextTest()
+        transaction.commit()
+
+        # submit missing values for required field s1
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : '',
+            'field.s2' : '',
+            'field.s3' : '' })
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm error msgs
+        self.assert_(missingInputErrorExists('s1', response.getBody()))
+        self.assert_(not missingInputErrorExists('s2', response.getBody()))
+        self.assert_(not missingInputErrorExists('s3', response.getBody()))
+
+
+    def test_length_validation(self):
+        self.getRootFolder()['test'] = TextTest()
+        transaction.commit()
+
+        # submit value for s1 that is too short
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : u'a' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists(
+            's1', 'Value is too short', response.getBody()))
+
+        # submit value for s1 that is too long
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : u'12345678901' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists('s1', 'Value is too long',
+            response.getBody()))
+
+
+    def test_omitted_value(self):
+        self.getRootFolder()['test'] = TextTest()
+        transaction.commit()
+
+        # confirm default values
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.s1, '')
+        self.assertEqual(object.s2, u'foo')
+        self.assert_(object.s3 is None)
+
+        # submit change with only s2 present -- note that required
+        # field s1 is omitted, which should not cause a validation error
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s2' : u'bar' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.s1, '')
+        self.assertEqual(object.s2, u'bar')
+        self.assert_(object.s3 is None)
+
+
+    def test_conversion(self):
+        self.getRootFolder()['test'] = TextTest()
+        transaction.commit()
+
+        # confirm that line terminators are converted correctly on post
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s2' : u'line1\r\nline2' }) # CRLF per RFC 822
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.s2, u'line1\nline2')
+
+        # confirm conversion to HTML
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(patternExists('line1\r\nline2', response.getBody()))
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Copied: zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_textwidget.py (from rev 81038, zope.app.form/trunk/src/zope/app/form/browser/ftests/test_textwidget.py)
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_textwidget.py	                        (rev 0)
+++ zope.app.form/trunk/src/zope/app/form/browser/tests/test_functional_textwidget.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -0,0 +1,230 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""TextWidget Tests
+
+$Id$
+"""
+import unittest
+import transaction
+from persistent import Persistent
+
+import zope.security.checker
+from zope.interface import Interface, implements
+from zope.schema import TextLine, Choice
+from zope.traversing.api import traverse
+
+from zope.app.form.testing import AppFormLayer
+from zope.app.form.browser.tests.support import *
+from zope.app.testing.functional import BrowserTestCase
+
+class ITextLineTest(Interface):
+
+    s2 = TextLine(
+        required=False,
+        missing_value=u'')
+
+    s3 = Choice(
+        required=False,
+        values=(u'Bob', u'is', u'Your', u'Uncle'))
+
+    s1 = TextLine(
+        required=True,
+        min_length=2,
+        max_length=10)
+
+
+class TextLineTest(Persistent):
+
+    implements(ITextLineTest)
+
+    def __init__(self):
+        self.s1 = ''
+        self.s2 = u'foo'
+        self.s3 = None
+
+
+class Test(BrowserTestCase):
+
+
+    def setUp(self):
+        BrowserTestCase.setUp(self)
+        registerEditForm(ITextLineTest)
+        defineSecurity(TextLineTest, ITextLineTest)
+
+    def test_display_editform(self):
+        self.getRootFolder()['test'] = TextLineTest()
+        transaction.commit()
+
+        # display edit view
+        response = self.publish('/test/edit.html')
+        self.assertEqual(response.getStatus(), 200)
+
+        # s1 and s2 should be displayed in text fields
+        self.assert_(patternExists(
+            '<input .* name="field.s1".* value="".*>', response.getBody()))
+        self.assert_(patternExists(
+            '<input .* name="field.s2".* value="foo".*>', response.getBody()))
+
+        # s3 should be in a dropdown
+        self.assert_(patternExists(
+            '<select .*name="field.s3".*>', response.getBody()))
+        self.assert_(patternExists(
+            '<option selected="selected" value="">.*</option>', response.getBody()))
+
+
+    def test_submit_editform(self):
+        self.getRootFolder()['test'] = TextLineTest()
+        transaction.commit()
+
+        # submit edit view
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : u'foo',
+            'field.s2' : u'bar',
+            'field.s3' : u'Uncle' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.s1, u'foo')
+        self.assertEqual(object.s2, u'bar')
+        self.assertEqual(object.s3, u'Uncle')
+
+
+    def test_invalid_type(self):
+        """Tests text widget's handling of invalid unicode input.
+
+        The text widget will succeed in converting any form input into
+        unicode.
+        """
+        self.getRootFolder()['test'] = TextLineTest()
+        transaction.commit()
+
+        # submit invalud type for text line
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : '' }) # not unicode (but automatically converted to it.
+        self.assertEqual(response.getStatus(), 200)
+
+        # We don't have a invalid field value
+        #since we convert the value to unicode
+        self.assert_(not validationErrorExists(
+            's1', 'Object is of wrong type.', response.getBody()))
+
+
+    def test_missing_value(self):
+        self.getRootFolder()['test'] = TextLineTest()
+        transaction.commit()
+
+        # submit missing values for s2 and s3
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : u'foo',
+            'field.s2' : u'',
+            'field.s3' : u'' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new values in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.s1, u'foo')
+        self.assertEqual(object.s2, u'')   # default missing_value
+        self.assertEqual(object.s3, None)  # None is s3's missing_value
+
+
+    def test_required_validation(self):
+        self.getRootFolder()['test'] = TextLineTest()
+        transaction.commit()
+
+        # submit missing values for required field s1
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : u'',
+            'field.s2' : u'',
+            'field.s3' : u'' })
+        self.assertEqual(response.getStatus(), 200)
+
+        # confirm error msgs
+        self.assert_(missingInputErrorExists('s1', response.getBody()))
+        self.assert_(not missingInputErrorExists('s2', response.getBody()))
+        self.assert_(not missingInputErrorExists('s3', response.getBody()))
+
+
+    def test_invalid_value(self):
+        self.getRootFolder()['test'] = TextLineTest()
+        transaction.commit()
+
+        # submit a value for s3 that isn't allowed
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s3' : u'Bob is *Not* My Uncle' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(invalidValueErrorExists('s3', response.getBody()))
+
+
+    def test_length_validation(self):
+        self.getRootFolder()['test'] = TextLineTest()
+        transaction.commit()
+
+        # submit value for s1 that is too short
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : u'a' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists(
+            's1', 'Value is too short', response.getBody()))
+
+        # submit value for s1 that is too long
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s1' : u'12345678901' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(validationErrorExists(
+            's1', 'Value is too long', response.getBody()))
+
+
+    def test_omitted_value(self):
+        self.getRootFolder()['test'] = TextLineTest()
+        transaction.commit()
+
+        # confirm default values
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.s1, '')
+        self.assertEqual(object.s2, u'foo')
+        self.assert_(object.s3 is None)
+
+        # submit change with only s2 present -- note that required
+        # field s1 is omitted, which should not cause a validation error
+        response = self.publish('/test/edit.html', form={
+            'UPDATE_SUBMIT' : '',
+            'field.s2' : u'bar' })
+        self.assertEqual(response.getStatus(), 200)
+        self.assert_(updatedMsgExists(response.getBody()))
+
+        # check new value in object
+        object = traverse(self.getRootFolder(), 'test')
+        self.assertEqual(object.s1, '')
+        self.assertEqual(object.s2, u'bar')
+        self.assert_(object.s3 is None)
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    Test.layer = AppFormLayer
+    suite.addTest(unittest.makeSuite(Test))
+    return suite
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Modified: zope.app.form/trunk/src/zope/app/form/browser/widgets.txt
===================================================================
--- zope.app.form/trunk/src/zope/app/form/browser/widgets.txt	2007-10-24 15:19:13 UTC (rev 81039)
+++ zope.app.form/trunk/src/zope/app/form/browser/widgets.txt	2007-10-24 15:27:12 UTC (rev 81040)
@@ -1,3 +1,4 @@
+============================================================
 Simple example showing ObjectWidget and SequenceWidget usage
 ============================================================
 

Modified: zope.app.form/trunk/src/zope/app/form/ftesting.zcml
===================================================================
--- zope.app.form/trunk/src/zope/app/form/ftesting.zcml	2007-10-24 15:19:13 UTC (rev 81039)
+++ zope.app.form/trunk/src/zope/app/form/ftesting.zcml	2007-10-24 15:27:12 UTC (rev 81040)
@@ -9,7 +9,7 @@
 
 
   <include package="zope.app.zcmlfiles" />
-  <include package="zope.app.form.browser.ftests" file="i18n.zcml" />
+  <include package="zope.app.form.browser.tests" file="i18n.zcml" />
 
   <include package="zope.app.securitypolicy" file="meta.zcml" />
   <include package="zope.app.authentication" />

Modified: zope.app.form/trunk/src/zope/app/form/utility.py
===================================================================
--- zope.app.form/trunk/src/zope/app/form/utility.py	2007-10-24 15:19:13 UTC (rev 81039)
+++ zope.app.form/trunk/src/zope/app/form/utility.py	2007-10-24 15:27:12 UTC (rev 81040)
@@ -57,16 +57,16 @@
     else:
         fields = [ (name, schema[name]) for name in names ]
     return fields
-    
-    
+
+
 def _createWidget(context, field, viewType, request):
-    """Creates a widget given a `context`, `field`, and `viewType`."""    
+    """Creates a widget given a `context`, `field`, and `viewType`."""
     field = field.bind(context)
     return component.getMultiAdapter((field, request), viewType)
 
 def _widgetHasStickyValue(widget):
     """Returns ``True`` if the widget has a sticky value.
-    
+
     A sticky value is input from the user that should not be overridden
     by an object's current field value. E.g. a user may enter an invalid
     postal code, submit the form, and receive a validation error - the postal
@@ -74,7 +74,7 @@
     the object.
     """
     return IInputWidget.providedBy(widget) and widget.hasInput()
-    
+
 def setUpWidget(view, name, field, viewType, value=no_value, prefix=None,
                 ignoreStickyValues=False, context=None):
     """Sets up a single view widget.
@@ -89,7 +89,7 @@
     if context is None:
         context = view.context
     widgetName = name + '_widget'
-    
+
     # check if widget already exists
     widget = getattr(view, widgetName, None)
     if widget is None:
@@ -100,16 +100,16 @@
         # exists, but is actually a factory - use it to create the widget
         widget = widget(field.bind(context), view.request)
         setattr(view, widgetName, widget)
-        
+
     # widget must implement IWidget
     if not IWidget.providedBy(widget):
         raise TypeError(
             "Unable to configure a widget for %s - attribute %s does not "
             "implement IWidget" % (name, widgetName))
-    
+
     if prefix:
         widget.setPrefix(prefix)
-        
+
     if value is not no_value and (
         ignoreStickyValues or not _widgetHasStickyValue(widget)):
         widget.setRenderedValue(value)
@@ -118,11 +118,11 @@
 def setUpWidgets(view, schema, viewType, prefix=None, ignoreStickyValues=False,
                  initial={}, names=None, context=None):
     """Sets up widgets for the fields defined by a `schema`.
-    
+
     Appropriate for collecting input without a current object implementing
     the schema (such as an add form).
 
-    `view` is the view that will be configured with widgets. 
+    `view` is the view that will be configured with widgets.
 
     `viewType` is the type of widgets to create (e.g. IInputWidget or
     IDisplayWidget).
@@ -146,34 +146,34 @@
     `context` provides an alternative context for acquisition.
     """
     for (name, field) in _fieldlist(names, schema):
-        setUpWidget(view, name, field, viewType, 
+        setUpWidget(view, name, field, viewType,
                     value=initial.get(name, no_value),
                     prefix=prefix,
-                    ignoreStickyValues=ignoreStickyValues, 
+                    ignoreStickyValues=ignoreStickyValues,
                     context=context)
 
 def setUpEditWidgets(view, schema, source=None, prefix=None,
                      ignoreStickyValues=False, names=None, context=None,
                      degradeInput=False, degradeDisplay=False):
     """Sets up widgets to collect input on a view.
-    
+
     See `setUpWidgets` for details on `view`, `schema`, `prefix`,
     `ignoreStickyValues`, `names`, and `context`.
-    
+
     `source`, if specified, is an object from which initial widget values are
     read. If source is not specified, the view context is used as the source.
-    
+
     `degradeInput` is a flag that changes the behavior when a user does not
     have permission to edit a field in the names.  By default, the function
     raises Unauthorized.  If degradeInput is True, the field is changed to
     an IDisplayWidget.
-    
+
     `degradeDisplay` is a flag that changes the behavior when a user does not
     have permission to access a field in the names.  By default, the function
     raises Unauthorized.  If degradeDisplay is True, the field is removed from
     the form.
-    
-    Returns a list of names, equal to or a subset of the names that were 
+
+    Returns a list of names, equal to or a subset of the names that were
     supposed to be drawn, with uninitialized undrawn fields missing.
     """
     if context is None:
@@ -225,23 +225,23 @@
         res_names.append(name)
     return res_names
 
-def setUpDisplayWidgets(view, schema, source=None, prefix=None, 
+def setUpDisplayWidgets(view, schema, source=None, prefix=None,
                         ignoreStickyValues=False, names=None, context=None,
                         degradeDisplay=False):
     """Sets up widgets to display field values on a view.
-    
+
     See `setUpWidgets` for details on `view`, `schema`, `prefix`,
     `ignoreStickyValues`, `names`, and `context`.
-    
+
     `source`, if specified, is an object from which initial widget values are
     read. If source is not specified, the view context is used as the source.
-    
+
     `degradeDisplay` is a flag that changes the behavior when a user does not
     have permission to access a field in the names.  By default, the function
     raises Unauthorized.  If degradeDisplay is True, the field is removed from
     the form.
-    
-    Returns a list of names, equal to or a subset of the names that were 
+
+    Returns a list of names, equal to or a subset of the names that were
     supposed to be drawn, with uninitialized undrawn fields missing.
     """
     if context is None:
@@ -268,9 +268,9 @@
 
 def viewHasInput(view, schema, names=None):
     """Returns ``True`` if the any of the view's widgets contain user input.
-    
+
     `schema` specifies the set of fields that correspond to the view widgets.
-    
+
     `names` can be specified to provide a subset of these fields.
     """
     for name, field in _fieldlist(names, schema):
@@ -280,14 +280,14 @@
 
 def applyWidgetsChanges(view, schema, target=None, names=None):
     """Updates an object with values from a view's widgets.
-    
+
     `view` contained the widgets that perform the update. By default, the
     widgets will update the view's context.
-    
+
     `target` can be specified as an alternative object to update.
-    
+
     `schema` contrains the values provided by the widgets.
-    
+
     `names` can be specified to update a subset of the schema constrained
     values.
     """
@@ -310,30 +310,30 @@
 
 def getWidgetsData(view, schema, names=None):
     """Returns user entered data for a set of `schema` fields.
-    
+
     The return value is a map of field names to data values.
-    
+
     `view` is the view containing the widgets. `schema` is the schema that
     defines the widget fields. An optional `names` argument can be provided
     to specify an alternate list of field values to return. If `names` is
     not specified, or is ``None``, `getWidgetsData` will attempt to return
     values for all of the fields in the schema.
-    
+
     A requested field value may be omitted from the result for one of two
     reasons:
-        
+
         - The field is read only, in which case its widget will not have
           user input.
-          
-        - The field is editable and not required but its widget does not 
+
+        - The field is editable and not required but its widget does not
           contain user input.
-    
+
     If a field is required and its widget does not have input, `getWidgetsData`
     raises an error.
-    
+
     A widget may raise a validation error if it cannot return a value that
     satisfies its field's contraints.
-    
+
     Errors, if any, are collected for all fields and reraised as a single
     `WidgetsError`.
     """
@@ -351,9 +351,9 @@
             elif field.required:
                 errors.append(MissingInputError(
                     name, widget.label, 'the field is required'))
-            
+
     if errors:
         raise WidgetsError(errors, widgetsData=result)
-        
+
     return result
 



More information about the Checkins mailing list