[Zope3-checkins] SVN: Zope3/branches/testbrowser-integration/src/zope/testbrowser/ - update the interfaces to match the implementation

Benji York benji at zope.com
Tue Aug 23 15:27:13 EDT 2005


Log message for revision 38050:
  - update the interfaces to match the implementation
  - add tests that verify interfaces that weren't being verified
  - make instances resist having *new* attributes accidentally set on them (with 
    tests)
  - add helpful argument verification for getForm
  - remove __getattr__ from Form in preference for properties
  

Changed:
  U   Zope3/branches/testbrowser-integration/src/zope/testbrowser/README.txt
  U   Zope3/branches/testbrowser-integration/src/zope/testbrowser/browser.py
  U   Zope3/branches/testbrowser-integration/src/zope/testbrowser/interfaces.py

-=-
Modified: Zope3/branches/testbrowser-integration/src/zope/testbrowser/README.txt
===================================================================
--- Zope3/branches/testbrowser-integration/src/zope/testbrowser/README.txt	2005-08-23 17:44:46 UTC (rev 38049)
+++ Zope3/branches/testbrowser-integration/src/zope/testbrowser/README.txt	2005-08-23 19:27:13 UTC (rev 38050)
@@ -25,6 +25,13 @@
 testbrowser doctests suggests using 'click' to navigate further (as discussed
 below), except in unusual circumstances.
 
+The test browser complies with the IBrowser interface.
+
+    >>> from zope.testbrowser import interfaces
+    >>> from zope.interface.verify import verifyObject
+    >>> verifyObject(interfaces.IBrowser, browser)
+    True
+
 Page Contents
 -------------
 
@@ -138,6 +145,14 @@
     >>> link = browser.getLink('Link Text')
     >>> link
     <Link text='Link Text' url='http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'>
+
+Link objects comply with the ILink interface.
+
+    >>> verifyObject(interfaces.ILink, link)
+    True
+
+Links expose several attributes for easy access.
+
     >>> link.text
     'Link Text'
     >>> link.tag # links can also be image maps.
@@ -146,6 +161,9 @@
     'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
     >>> link.attrs
     {'href': 'navigate.html?message=By+Link+Text'}
+
+Links can be "clicked" and the brower will navigate to the refrenced URL.
+
     >>> link.click()
     >>> browser.url
     'http://localhost/@@/testbrowser/navigate.html?message=By+Link+Text'
@@ -261,7 +279,8 @@
 argument is 'label', and looks up the form on the basis of any associated
 label.
 
-    >>> browser.getControl('Text Control')
+    >>> control = browser.getControl('Text Control')
+    >>> control
     <Control name='text-value' type='text'>
     >>> browser.getControl(label='Text Control') # equivalent
     <Control name='text-value' type='text'>
@@ -384,8 +403,6 @@
     >>> ctrl = browser.getControl('Text Control')
     >>> ctrl
     <Control name='text-value' type='text'>
-    >>> from zope.interface.verify import verifyObject
-    >>> from zope.testbrowser import interfaces
     >>> verifyObject(interfaces.IControl, ctrl)
     True
 
@@ -887,6 +904,11 @@
     >>> browser.open('http://localhost/@@/testbrowser/forms.html')
     >>> form = browser.getForm(name='one')
 
+Form instances conform to the IForm interface.
+
+    >>> verifyObject(interfaces.IForm, form)
+    True
+
 The form exposes several attributes related to forms:
 
   - The name of the form:
@@ -1010,3 +1032,29 @@
     ...
     NotFound: Object: <zope.app.folder.folder.Folder object at ...>,
               name: u'invalid'
+
+Hand-Holding
+------------
+
+Instances of the various objects ensure that users don't accidentally set
+instance attributes accidentally.
+
+    >>> browser.nonexistant = None
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'Browser' object has no attribute 'nonexistant'
+
+    >>> form.nonexistant = None
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'Form' object has no attribute 'nonexistant'
+
+    >>> control.nonexistant = None
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'Control' object has no attribute 'nonexistant'
+
+    >>> link.nonexistant = None
+    Traceback (most recent call last):
+    ...
+    AttributeError: 'Link' object has no attribute 'nonexistant'

Modified: Zope3/branches/testbrowser-integration/src/zope/testbrowser/browser.py
===================================================================
--- Zope3/branches/testbrowser-integration/src/zope/testbrowser/browser.py	2005-08-23 17:44:46 UTC (rev 38049)
+++ Zope3/branches/testbrowser-integration/src/zope/testbrowser/browser.py	2005-08-23 19:27:13 UTC (rev 38050)
@@ -59,6 +59,9 @@
         else:
             return Control(control, form, browser)
 
+def any(items):
+    return bool(sum([bool(i) for i in items]))
+
 def onlyOne(items, description):
     total = sum([bool(i) for i in items])
     if total == 0 or total > 1:
@@ -71,7 +74,19 @@
             "Supply no more than one of %s as arguments" % description)
 
 
-class Browser(object):
+class SetattrErrorsMixin(object):
+    _enable_setattr_errors = False
+
+    def __setattr__(self, name, value):
+        if self._enable_setattr_errors:
+            # cause an attribute error if the attribute doesn't already exist
+            getattr(self, name)
+
+        # set the value
+        object.__setattr__(self, name, value)
+
+
+class Browser(SetattrErrorsMixin):
     """A web user agent."""
     zope.interface.implements(interfaces.IBrowser)
 
@@ -82,6 +97,7 @@
         self._counter = 0
         if url is not None:
             self.open(url)
+        self._enable_setattr_errors = True
 
     @property
     def url(self):
@@ -227,6 +243,9 @@
         
     def getForm(self, id=None, name=None, action=None, index=None):
         zeroOrOne([id, name, action], '"id", "name", and "action"')
+        if index is None and not any([id, name, action]):
+            raise ValueError(
+                'if no other arguments are given, index is required.')
 
         matching_forms = []
         for form in self.mech_browser.forms():
@@ -248,13 +267,14 @@
         self._counter += 1
 
 
-class Link(object):
+class Link(SetattrErrorsMixin):
     zope.interface.implements(interfaces.ILink)
 
     def __init__(self, link, browser):
         self.mech_link = link
         self.browser = browser
         self._browser_counter = self.browser._counter
+        self._enable_setattr_errors = True
 
     def click(self):
         if self._browser_counter != self.browser._counter:
@@ -283,15 +303,18 @@
             self.__class__.__name__, self.text, self.url)
 
 
-class Control(object):
+class Control(SetattrErrorsMixin):
     """A control of a form."""
     zope.interface.implements(interfaces.IControl)
 
+    _enable_setattr_errors = False
+
     def __init__(self, control, form, browser):
         self.mech_control = control
         self.mech_form = form
         self.browser = browser
         self._browser_counter = self.browser._counter
+        self._enable_setattr_errors = True
 
         # for some reason ClientForm thinks we shouldn't be able to modify
         # hidden fields, but while testing it is sometimes very important
@@ -436,7 +459,7 @@
         self.browser._changed()
 
 
-class ItemControl(object):
+class ItemControl(SetattrErrorsMixin):
     zope.interface.implements(interfaces.IItemControl)
 
     def __init__(self, item, form, browser):
@@ -444,6 +467,7 @@
         self.mech_form = form
         self.browser = browser
         self._browser_counter = self.browser._counter
+        self._enable_setattr_errors = True
 
     @property
     def control(self):
@@ -487,7 +511,7 @@
             self.mech_item.control.type, self.optionValue)
 
 
-class Form(object):
+class Form(SetattrErrorsMixin):
     """HTML Form"""
     zope.interface.implements(interfaces.IForm)
 
@@ -500,16 +524,25 @@
         self.browser = browser
         self.mech_form = form
         self._browser_counter = self.browser._counter
+        self._enable_setattr_errors = True
     
-    def __getattr__(self, name):
-        # See zope.testbrowser.interfaces.IForm
-        names = ['action', 'method', 'enctype', 'name']
-        if name in names:
-            return getattr(self.mech_form, name, None)
-        else:
-            raise AttributeError(name)
+    @property
+    def action(self):
+        return self.mech_form.action
 
     @property
+    def method(self):
+        return self.mech_form.method
+
+    @property
+    def enctype(self):
+        return self.mech_form.enctype
+
+    @property
+    def name(self):
+        return self.mech_form.name
+
+    @property
     def id(self):
         """See zope.testbrowser.interfaces.IForm"""
         return self.mech_form.attrs.get('id')

Modified: Zope3/branches/testbrowser-integration/src/zope/testbrowser/interfaces.py
===================================================================
--- Zope3/branches/testbrowser-integration/src/zope/testbrowser/interfaces.py	2005-08-23 17:44:46 UTC (rev 38049)
+++ Zope3/branches/testbrowser-integration/src/zope/testbrowser/interfaces.py	2005-08-23 19:27:13 UTC (rev 38050)
@@ -155,8 +155,6 @@
         description=u'The tag name of the link (a or area, typically)',
         required=True)
 
-class IFormsMapping(zope.interface.common.mapping.IReadMapping):
-    """A mapping of all forms in a page."""
 
 class IForm(zope.interface.Interface):
     """An HTML form of the page."""
@@ -254,14 +252,6 @@
         description=u"Title of the displayed page",
         required=False)
 
-    forms = zope.schema.Object(
-        title=u"Forms",
-        description=(u"A mapping of form elements on the page. The key is "
-                     u"actually quiet flexible and searches the id and name "
-                     u"of the form element."),
-        schema=IFormsMapping,
-        required=True)
-
     handleErrors = zope.schema.Bool(
         title=u"Handle Errors",
         description=(u"Describes whether server-side errors will be handled "
@@ -319,7 +309,7 @@
         """
 
     def getControl(label=None, name=None, index=None):
-        """Get a control in the page.
+        """Get a control from the page.
 
         Only one of ``label`` and ``name`` may be provided.  ``label``
         searches form labels (including submit button values, per the HTML 4.0
@@ -337,3 +327,18 @@
         it is used to choose the index from the ambiguous choices.  If the
         index does not exist, the code raises a LookupError.
         """
+
+    def getForm(id=None, name=None, action=None, index=None):
+        """Get a form from the page.
+
+        Zero or one of ``id``, ``name``, and ``action`` may be provided.  If
+        none are provided the index alone is used to determine the return
+        value.
+
+        If no values are found, the code raises a LookupError.
+
+        If ``index`` is None (the default) and more than one form matches the
+        search, the code raises an AmbiguityError.  If an index is provided,
+        it is used to choose the index from the ambiguous choices.  If the
+        index does not exist, the code raises a LookupError.
+        """



More information about the Zope3-Checkins mailing list