You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2007/06/27 20:33:30 UTC

svn commit: r551256 [1/2] - in /tapestry/tapestry5/trunk/tapestry-core/src: main/java/org/apache/tapestry/internal/services/ main/java/org/apache/tapestry/services/ main/java/org/apache/tapestry/test/ main/java/org/apache/tapestry/validator/ main/resou...

Author: hlship
Date: Wed Jun 27 11:33:28 2007
New Revision: 551256

URL: http://svn.apache.org/viewvc?view=rev&rev=551256
Log:
TAPESTRY-1610: Implement regular expression based input validation (client and server)

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/Regexp.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/RegexpDemo.html
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/RegexpDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/validator/RegexpTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/pages/RegexpDemo.properties
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/MaxLength.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/ValidationMessages.properties
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
    tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/tapestry.js
    tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/services/FieldValidatorSourceImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/pages/BeanEditorDemo.properties
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry/integration/app1/pages/ValidForm.properties

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/FieldValidatorSourceImpl.java Wed Jun 27 11:33:28 2007
@@ -90,7 +90,27 @@
                     validatorType,
                     InternalUtils.sortedKeys(_validators)));
 
-        Object coercedConstraintValue = coerceConstraintValue(constraintValue, validator
+        // I just have this thing about always treating parameters as finals, so
+        // we introduce a second variable to treat a mutable.
+
+        String finalConstraintValue = constraintValue;
+
+        // If no constraint was provided, check to see if it is available via a localized message
+        // key. This is really handy for complex validations such as patterns.
+
+        if (finalConstraintValue == null && validator.getConstraintType() != null)
+        {
+            String key = overrideId + "-" + validatorType;
+
+            if (overrideMessages.contains(key))
+                finalConstraintValue = overrideMessages.get(key);
+            else
+                throw new IllegalArgumentException(ServicesMessages.missingValidatorConstraint(
+                        validatorType,
+                        validator.getConstraintType()));
+        }
+
+        Object coercedConstraintValue = coerceConstraintValue(finalConstraintValue, validator
                 .getConstraintType());
 
         MessageFormatter formatter = findMessageFormatter(
@@ -108,7 +128,7 @@
             Locale locale, String validatorType, Validator validator)
     {
 
-        String overrideKey = overrideId + "-" + validatorType;
+        String overrideKey = overrideId + "-" + validatorType + "-message";
 
         if (overrideMessages.contains(overrideKey))
             return overrideMessages.getFormatter(overrideKey);

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/internal/services/ServicesMessages.java Wed Jun 27 11:33:28 2007
@@ -35,10 +35,6 @@
 {
     private static final Messages MESSAGES = MessagesImpl.forClass(ServicesMessages.class);
 
-    private ServicesMessages()
-    {
-    }
-
     static final String duplicateContribution(Object conflict, Class contributionType,
             Object existing)
     {
@@ -193,7 +189,7 @@
         return MESSAGES.format("could-not-resolve-page-name", pageName, InternalUtils
                 .joinSorted(pageNames));
     }
-    
+
     static String couldNotCanonicalizePageName(String pageName, Collection<String> pageNames)
     {
         return MESSAGES.format("could-not-canonicalize-page-name", pageName, InternalUtils
@@ -403,5 +399,10 @@
     static String noEditForDataType(String datatype)
     {
         return MESSAGES.format("no-edit-for-data-type", datatype);
+    }
+
+    static String missingValidatorConstraint(String validatorType, Class type)
+    {
+        return MESSAGES.format("missing-validator-constraint", validatorType, type.getName());
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java Wed Jun 27 11:33:28 2007
@@ -20,6 +20,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Pattern;
 
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
@@ -197,6 +198,7 @@
 import org.apache.tapestry.validator.MaxLength;
 import org.apache.tapestry.validator.Min;
 import org.apache.tapestry.validator.MinLength;
+import org.apache.tapestry.validator.Regexp;
 import org.apache.tapestry.validator.Required;
 
 /**
@@ -560,6 +562,7 @@
      * <li>maxlength</li>
      * <li>min</li>
      * <li>max</li>
+     * <li>regexp</li>
      * </ul>
      */
     public static void contributeFieldValidatorSource(
@@ -570,6 +573,7 @@
         configuration.add("maxlength", new MaxLength());
         configuration.add("min", new Min());
         configuration.add("max", new Max());
+        configuration.add("regexp", new Regexp());
     }
 
     /**
@@ -775,6 +779,15 @@
                 return TapestryInternalUtils.toSelectModel(input);
             }
         });
+
+        add(configuration, String.class, Pattern.class, new Coercion<String, Pattern>()
+        {
+            public Pattern coerce(String input)
+            {
+                return Pattern.compile(input);
+            }
+        });
+
     }
 
     /**
@@ -1520,5 +1533,5 @@
                 configuration,
                 resources.autobuild(ComponentActionRequestHandlerImpl.class));
     }
-    
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/test/TapestryTestCase.java Wed Jun 27 11:33:28 2007
@@ -599,7 +599,7 @@
 
     protected <C, T> void train_getConstraintType(Validator<C, T> validator, Class<C> constraintType)
     {
-        expect(validator.getConstraintType()).andReturn(constraintType);
+        expect(validator.getConstraintType()).andReturn(constraintType).atLeastOnce();
     }
 
     protected final void train_getContainer(ComponentResources resources, Component container)

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/MaxLength.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/MaxLength.java?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/MaxLength.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/MaxLength.java Wed Jun 27 11:33:28 2007
@@ -60,6 +60,9 @@
     public void render(Field field, Integer constraintValue, MessageFormatter formatter,
             MarkupWriter writer, PageRenderSupport pageRenderSupport)
     {
+        // TODO: write a maxlength attribute into the element?  But that's only for
+        // textfield, not for textarea.
+        
         pageRenderSupport.addScript(
                 "Tapestry.Field.maxlength('%s', %d, %s);",
                 field.getClientId(),

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/Regexp.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/Regexp.java?view=auto&rev=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/Regexp.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/validator/Regexp.java Wed Jun 27 11:33:28 2007
@@ -0,0 +1,78 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.validator;
+
+import static org.apache.tapestry.TapestryUtils.quote;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tapestry.Field;
+import org.apache.tapestry.MarkupWriter;
+import org.apache.tapestry.PageRenderSupport;
+import org.apache.tapestry.ValidationException;
+import org.apache.tapestry.Validator;
+import org.apache.tapestry.ioc.MessageFormatter;
+
+public class Regexp implements Validator<Pattern, String>
+{
+    public Class<Pattern> getConstraintType()
+    {
+        return Pattern.class;
+    }
+
+    public String getMessageKey()
+    {
+        return "regexp";
+    }
+
+    public Class<String> getValueType()
+    {
+        return String.class;
+    }
+
+    public boolean invokeIfBlank()
+    {
+        return false;
+    }
+
+    private String buildMessage(MessageFormatter formatter, Field field, Pattern constraintValue)
+    {
+        return formatter.format(constraintValue.toString(), field.getLabel());
+    }
+
+    public void render(Field field, Pattern constraintValue, MessageFormatter formatter,
+            MarkupWriter writer, PageRenderSupport pageRenderSupport)
+    {
+        String clientPattern = Pattern.quote(constraintValue.pattern());
+
+        pageRenderSupport.addScript(
+                "Tapestry.Field.regexp('%s', %s, %s);",
+                field.getClientId(),
+                quote(clientPattern),
+                quote(buildMessage(formatter, field, constraintValue)));
+
+    }
+
+    public void validate(Field field, Pattern constraintValue, MessageFormatter formatter,
+            String value) throws ValidationException
+    {
+        Matcher matcher = constraintValue.matcher(value);
+
+        if (!matcher.matches())
+            throw new ValidationException(buildMessage(formatter, field, constraintValue));
+    }
+
+}
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/ValidationMessages.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/ValidationMessages.properties?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/ValidationMessages.properties (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/ValidationMessages.properties Wed Jun 27 11:33:28 2007
@@ -21,6 +21,9 @@
 maximum-string-length=You may provide at most %d characters for %s.
 min-integer=%2$s requires a value of at least %1$d. 
 max-integer=%2$s requires a value no larger than %1$d.
+# This is lousy as a default, since the pattern string is meaningless to the user. You should always override
+# this.
+regexp=%2$s does not match pattern '%1$s'.
 
 # This is where the translator messages go.
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/internal/services/ServicesStrings.properties Wed Jun 27 11:33:28 2007
@@ -79,4 +79,5 @@
   This may indicate that too much state is being stored, which can cause the encoded string to be truncated by the client web browser.
 unclosed-attribute-expression=Attribute expression '%s' is missing a closing brace.
 no-display-for-data-type=There is no defined way to display data of type '%s'. Make a contribution to the BeanBlockSource service for this type.
-no-edit-for-data-type=There is no defined way to edit data of type '%s'.  Make a contribution to the BeanBlockSource service for this type.
\ No newline at end of file
+no-edit-for-data-type=There is no defined way to edit data of type '%s'.  Make a contribution to the BeanBlockSource service for this type.
+missing-validator-constraint=Validator '%s' requires a validation constraint (of type %s) but none was provided.

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/tapestry.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/tapestry.js?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/tapestry.js (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry/tapestry.js Wed Jun 27 11:33:28 2007
@@ -150,6 +150,13 @@
       if (value > maxValue)
         event.recordError(message);
     });
+  },
+  
+  regexp : function(field, pattern, message) {
+    Tapestry.addValidator(field, false, function(value, event) {
+      if (! new Pattern(pattern).matches(value))
+        event.recordError(message);
+    });
   }  
 };
 

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/RegexpDemo.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/RegexpDemo.html?view=auto&rev=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/RegexpDemo.html (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/RegexpDemo.html Wed Jun 27 11:33:28 2007
@@ -0,0 +1,11 @@
+<html t:type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+
+<t:beaneditform object="this"/>
+
+<hr/>
+
+  <t:if test="zipCode">
+    Zip code: [${zipCode}]
+  </t:if>
+
+</html>  
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/app1/WEB-INF/Start.html Wed Jun 27 11:33:28 2007
@@ -135,6 +135,9 @@
           <li>
             <t:pagelink page="RadioDemo">RadioDemo</t:pagelink> -- Use of the RadioGroup and Radio
             components. </li>
+          <li>
+            <t:pagelink page="RegexpDemo">Regexp Demo</t:pagelink> -- Use of the Regexp validator
+          </li>
         </ul>
       </td>
     </tr>

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/IntegrationTests.java Wed Jun 27 11:33:28 2007
@@ -12,760 +12,760 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package org.apache.tapestry.integration;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.net.URL;
-
-import org.apache.tapestry.corelib.mixins.RenderDisabled;
-import org.apache.tapestry.internal.services.InjectComponentWorker;
-import org.apache.tapestry.ioc.Resource;
-import org.apache.tapestry.ioc.internal.util.ClasspathResource;
-import org.apache.tapestry.test.AbstractIntegrationTestSuite;
-import org.testng.annotations.Test;
-
-/**
- * Note: If these tests fail with BindException when starting Jetty, it could be Skype. At least on
- * my system, Skype is listening on localhost:80.
- */
-@Test(timeOut = 50000, sequential = true, groups =
-{ "integration" })
-public class IntegrationTests extends AbstractIntegrationTestSuite
-{
-    public IntegrationTests()
-    {
-        super("src/test/app1");
-    }
-
-    @Test
-    public void assets() throws Exception
-    {
-        open(BASE_URL);
-        clickAndWait("link=AssetDemo");
-
-        assertText("//img[@id='icon']/@src", "/images/tapestry_banner.gif");
-
-        // This doesn't prove that the image shows up in the client browser (it does, but
-        // it could just as easily be a broken image). Haven't figured out how Selenium
-        // allows this to be verified. Note that the path below represents some aliasing
-        // of the raw classpath resource path.
-
-        assertText("//img[@id='button']/@src", "/assets/app1/pages/tapestry-button.png");
-
-        // Read the byte stream for the asset and compare to the real copy.
-
-        URL url = new URL("http", "localhost", JETTY_PORT, "/assets/app1/pages/tapestry-button.png");
-
-        byte[] downloaded = readContent(url);
-
-        Resource classpathResource = new ClasspathResource(
-                "org/apache/tapestry/integration/app1/pages/tapestry-button.png");
-
-        byte[] actual = readContent(classpathResource.toURL());
-
-        assertEquals(downloaded, actual);
-    }
-
-    @Test
-    public void basic_parameters() throws Exception
-    {
-
-        // OK ... this could be a separate test, but for efficiency, we'll mix it in here.
-        // It takes a while to start up Selenium RC (and a Firefox browser).
-
-        open(BASE_URL);
-
-        clickAndWait("link=Count Page");
-
-        assertTextPresent("Ho! Ho! Ho!");
-    }
-
-    /**
-     * Tests the ability to inject a Block, and the ability to use the block to control rendering.
-     */
-    @Test
-    public void block_rendering() throws Exception
-    {
-        open(BASE_URL);
-        clickAndWait("link=BlockDemo");
-
-        assertTextPresent("[]");
-
-        select("//select[@id='blockName']", "fred");
-        waitForPageToLoad(PAGE_LOAD_TIMEOUT);
-
-        assertTextPresent("[Block fred.]");
-
-        select("//select[@id='blockName']", "barney");
-        waitForPageToLoad(PAGE_LOAD_TIMEOUT);
-
-        assertTextPresent("[Block barney.]");
+package org.apache.tapestry.integration;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.tapestry.corelib.mixins.RenderDisabled;
+import org.apache.tapestry.internal.services.InjectComponentWorker;
+import org.apache.tapestry.ioc.Resource;
+import org.apache.tapestry.ioc.internal.util.ClasspathResource;
+import org.apache.tapestry.test.AbstractIntegrationTestSuite;
+import org.testng.annotations.Test;
+
+/**
+ * Note: If these tests fail with BindException when starting Jetty, it could be Skype. At least on
+ * my system, Skype is listening on localhost:80.
+ */
+@Test(timeOut = 50000, sequential = true, groups =
+{ "integration" })
+public class IntegrationTests extends AbstractIntegrationTestSuite
+{
+    public IntegrationTests()
+    {
+        super("src/test/app1");
+    }
+
+    @Test
+    public void assets() throws Exception
+    {
+        open(BASE_URL);
+        clickAndWait("link=AssetDemo");
+
+        assertText("//img[@id='icon']/@src", "/images/tapestry_banner.gif");
+
+        // This doesn't prove that the image shows up in the client browser (it does, but
+        // it could just as easily be a broken image). Haven't figured out how Selenium
+        // allows this to be verified. Note that the path below represents some aliasing
+        // of the raw classpath resource path.
+
+        assertText("//img[@id='button']/@src", "/assets/app1/pages/tapestry-button.png");
+
+        // Read the byte stream for the asset and compare to the real copy.
+
+        URL url = new URL("http", "localhost", JETTY_PORT, "/assets/app1/pages/tapestry-button.png");
+
+        byte[] downloaded = readContent(url);
+
+        Resource classpathResource = new ClasspathResource(
+                "org/apache/tapestry/integration/app1/pages/tapestry-button.png");
+
+        byte[] actual = readContent(classpathResource.toURL());
+
+        assertEquals(downloaded, actual);
+    }
+
+    @Test
+    public void basic_parameters() throws Exception
+    {
+
+        // OK ... this could be a separate test, but for efficiency, we'll mix it in here.
+        // It takes a while to start up Selenium RC (and a Firefox browser).
+
+        open(BASE_URL);
+
+        clickAndWait("link=Count Page");
+
+        assertTextPresent("Ho! Ho! Ho!");
+    }
+
+    /**
+     * Tests the ability to inject a Block, and the ability to use the block to control rendering.
+     */
+    @Test
+    public void block_rendering() throws Exception
+    {
+        open(BASE_URL);
+        clickAndWait("link=BlockDemo");
+
+        assertTextPresent("[]");
+
+        select("//select[@id='blockName']", "fred");
+        waitForPageToLoad(PAGE_LOAD_TIMEOUT);
+
+        assertTextPresent("[Block fred.]");
+
+        select("//select[@id='blockName']", "barney");
+        waitForPageToLoad(PAGE_LOAD_TIMEOUT);
+
+        assertTextPresent("[Block barney.]");
 
         // TAPESETRY-1583
-        
-        assertTextPresent("before it is defined: [Block wilma].");
-    }
-
-    @Test
-    public void component_parameter_default_from_method() throws Exception
-    {
-        open(BASE_URL);
-        clickAndWait("link=ParameterDefault");
-
-        assertTextPresent("Echo component default: [ParameterDefault:echo]");
-    }
-
-    @Test
-    public void embedded_components()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Countdown Page");
-
-        assertTextPresent("regexp:\\s+5\\s+4\\s+3\\s+2\\s+1\\s+");
-
-        assertTextPresent("Brought to you by the org.apache.tapestry.integration.app1.components.Count");
-    }
-
-    @Test
-    public void encoded_loop_inside_a_form()
-    {
-        test_loop_inside_form("ToDo List");
-    }
-
-    @Test
-    public void environmental()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Environmental Annotation Useage");
-
-        assertSourcePresent("[<strong>A message provided by the RenderableProvider component.</strong>]");
-    }
-
-    @Test
-    public void exception_report()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=BadTemplate Page");
-
-        assertTextPresent(
-                "org.apache.tapestry.ioc.internal.util.TapestryException",
-                "Failure parsing template classpath:org/apache/tapestry/integration/app1/pages/BadTemplate.html, line 7, column 15",
-                "<t:foobar>content from template</t:foobar>",
-                "Element <t:foobar> is in the Tapestry namespace, but is not a recognized Tapestry template element.");
-    }
-
-    @Test
-    public void expansion()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Expansion Page");
-
-        assertTextPresent("[value provided by a template expansion]");
-    }
-
-    /**
-     * {@link InjectComponentWorker} is largely tested by the forms tests ({@link RenderDisabled}
-     * is built on it). This test is for the failure case, where a mixin class is used with the
-     * wrong type of component.
-     */
-    @Test
-    public void inject_component_failure() throws Exception
-    {
-        open(BASE_URL);
-        clickAndWait("link=InjectComponentMismatch");
-
-        // And exception message:
-
-        assertTextPresent("Component InjectComponentMismatch is not assignable to field org.apache.tapestry.corelib.mixins.RenderDisabled._field (of type org.apache.tapestry.Field).");
-    }
-
-    @Test
-    public void injection() throws Exception
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Inject Demo");
-
-        // This is a test for a named @Inject:
-        assertTextPresent("<Proxy for Request(org.apache.tapestry.services.Request)>");
-
-        // This is a test for an anonymous @Inject and ComponentResourcesInjectionProvider
-        assertTextPresent("ComponentResources[InjectDemo]");
-
-        // Another test, DefaultInjectionProvider
-        assertTextPresent("<Proxy for BindingSource(org.apache.tapestry.services.BindingSource)>");
-    }
-
-    @Test
-    public void instance_mixin()
-    {
-        open(BASE_URL);
-
-        final String[] dates =
-        { "Jun 13, 1999", "Jul 15, 2001", "Dec 4, 2005" };
-
-        clickAndWait("link=InstanceMixin");
-
-        for (String date : dates)
-        {
-            String snippet = String.format("[%s]", date);
-
-            assertSourcePresent(snippet);
-        }
-
-        clickAndWait("link=Toggle emphasis");
-
-        for (String date : dates)
-        {
-            String snippet = String.format("[<em>%s</em>]", date);
-            assertSourcePresent(snippet);
-        }
-    }
-
-    @Test
-    public void localization()
-    {
-        open(BASE_URL);
-        clickAndWait("link=Localization");
-
-        assertTextPresent("Via injected Messages property: [Accessed via injected Messages]");
-        assertTextPresent("Via message: binding prefix: [Accessed via message: binding prefix]");
-        assertTextPresent("From Application Message Catalog: [Application Catalog Working]");
-        assertTextPresent("Page locale: [en]");
-        clickAndWait("link=French");
-        assertTextPresent("Page locale: [fr]");
-        clickAndWait("link=English");
-        assertTextPresent("Page locale: [en]");
-    }
-
-    @Test
-    public void page_injection() throws Exception
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Inject Demo");
-
-        clickAndWait("link=Fred");
-
-        assertTextPresent("You clicked Fred.");
-
-        clickAndWait("link=Back");
-        clickAndWait("link=Barney");
-
-        assertTextPresent("You clicked Barney.");
-
-        clickAndWait("link=Back");
-        clickAndWait("link=Wilma");
-        assertTextPresent("You clicked Wilma.");
-    }
-
-    @Test
-    public void passivate_activate() throws Exception
-    {
-        open(BASE_URL);
-        clickAndWait("link=NumberSelect");
-        clickAndWait("link=5");
-        assertTextPresent("You chose 5.");
-    }
-
-    @Test
-    public void password_field()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=PasswordFieldDemo");
-
-        type("userName", "howard");
-        type("password", "wrong-password");
-
-        clickAndWait("//input[@type='submit']");
-
-        assertFieldValue("userName", "howard");
-        // Verify that password fields do not render a non-blank password, even when it is known.
-        assertFieldValue("password", "");
-
-        assertTextPresent("[howard]");
-        assertTextPresent("[wrong-password]");
-
-        type("password", "tapestry");
-
-        clickAndWait("//input[@type='submit']");
-
-        assertTextPresent("You have provided the correct user name and password.");
-    }
-
-    @Test
-    public void render_phase_method_returns_a_component() throws Exception
-    {
-        open(BASE_URL);
-        clickAndWait("link=RenderComponentDemo");
-
-        assertText("//span[@id='container']", "[]");
-
-        // Sneak in a little test for If and parameter else:
-
-        assertTextPresent("Should be blank:");
-
-        clickAndWait("enabled");
-
-        // After clicking the link (which submits the form), the page re-renders and shows us
-        // the optional component from inside the NeverRender, resurrected to render on the page
-        // after all.
-
-        assertText("//span[@id='container']/span", "Optional Text");
-
-        assertTextPresent("Should now show up:");
-    }
-
-    @Test
-    public void render_phase_order()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=RenderPhaseOrder");
-
-        assertTextPresent("[BEGIN-TRACER-MIXIN BEGIN-ABSTRACT-TRACER BEGIN-TRACER BODY AFTER-TRACER AFTER-ABSTRACT-TRACER AFTER-TRACER-MIXIN]");
-    }
-
-    @Test
-    public void server_side_validation_for_textfield_and_textarea() throws Exception
-    {
-        open(BASE_URL);
-        clickAndWait("link=ValidForm");
-        clickAndWait("//input[@type='submit']");
-        assertTextPresent("You must provide a value for Email.");
-        // This is an overrdden validation error message:
-        assertTextPresent("Please provide a detailed description of the incident.");
-
-        // Check on decorations via the default validation decorator:
-
-        assertText("//label[1]/@class", "t-error");
-        assertText("//label[2]/@class", "t-error");
-        assertText("//input[@id='email']/@class", "t-error");
-        assertText("//textarea[@id='message']/@class", "t-error");
-
-        type("email", "foo@bar.baz");
-        type("message", "Show me the money!");
-        type("hours", "foo");
-
-        clickAndWait("//input[@type='submit']");
-
-        assertTextPresent("[false]");
-        assertTextPresent("The input value 'foo' is not parseable as an integer value.");
-
-        assertText("//input[@id='hours']/@value", "foo");
-
-        type("hours", " 19 ");
-        click("//input[@id='urgent']");
-        clickAndWait("//input[@type='submit']");
-
-        // Make sure the decoration went away.
-
-        // Sorry, not sure how to do that, since the attributes don't exist, we get xpath errors.
-
-        // assertText("//label[1]/@class", "");
-        // assertText("//label[2]/@class", "");
-        // assertText("//input[@id='email']/@class", "");
-        // assertText("//textarea[@id='message']/@class", "");
-
-        assertTextPresent("[foo@bar.baz]");
-        assertTextPresent("[Show me the money!]");
-        assertTextPresent("[true]");
-        assertTextPresent("[19]");
-    }
-
-    @Test
-    public void simple_component_event()
-    {
-        final String YOU_CHOSE = "You chose: ";
-
-        open(BASE_URL);
-
-        clickAndWait("link=Action Page");
-
-        assertFalse(isTextPresent(YOU_CHOSE));
-
-        for (int i = 2; i < 5; i++)
-        {
-            clickAndWait("link=" + i);
-
-            assertTextPresent(YOU_CHOSE + i);
-        }
-    }
-
-    /**
-     * Tests for forms and form submissions and basic form control components. This also tests a few
-     * other things, such as computed default bindings and invisible instrumentation.
-     */
-    @Test
-    public void simple_form()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=SimpleForm");
-
-        assertText("//label[@id='disabled:label']", "Disabled");
-        assertText("//label[@id='email:label']", "Email");
-        assertText("//label[@id='message:label']", "Incident Message");
-        assertText("//label[@id='operatingSystem:label']", "Operating System");
-        assertText("//label[@id='department:label']", "Department");
-        assertText("//label[@id='urgent:label']", "Urgent Processing Requested");
-
-        assertFieldValue("email", "");
-        assertFieldValue("message", "");
-        assertFieldValue("operatingSystem", "osx");
-        assertFieldValue("department", "ACCOUNTING");
-        assertFieldValue("urgent", "on");
-
-        type("email", "foo@bar.baz");
-        type("message", "Message for you, sir!");
-        select("operatingSystem", "Windows NT");
-        select("department", "R&D");
-        click("urgent");
-
-        clickAndWait("//input[@type='submit']");
-
-        assertFieldValue("email", "foo@bar.baz");
-        assertFieldValue("message", "Message for you, sir!");
-        assertFieldValue("urgent", "off");
-
-        // Tried to use "email:" and "exact:email:" but Selenium 0.8.1 doesn't seem to accept that.
-
-        assertTextPresent(
-                "[foo@bar.baz]",
-                "[Message for you, sir!]",
-                "[false]",
-                "[winnt]",
-                "[RESEARCH_AND_DESIGN]");
-
-        // Haven't figured out how to get selenium to check that fields are disabled.
-    }
-
-    @Test
-    public void subclass_inherits_parent_template()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=ExpansionSubclass");
-
-        assertTextPresent("[value provided, in the subclass, via a template expansion]");
-    }
-
-    @Test
-    public void template_overridden()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Template Overriden by Class Page");
-
-        assertTextPresent("Output: ClassValue");
-    }
-
-    @Test
-    public void volatile_loop_inside_a_form()
-    {
-        test_loop_inside_form("ToDo List (Volatile)");
-    }
-
-    /** This also verifies the use of meta data to set the default strategy. */
-    @Test
-    public void flash_persistence()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=FlashDemo");
-
-        assertTextPresent("[]");
-
-        clickAndWait("show");
-
-        assertTextPresent("[You clicked the link!]");
-
-        clickAndWait("refresh");
-
-        assertTextPresent("[]");
-    }
-
-    private byte[] readContent(URL url) throws Exception
-    {
-        InputStream is = new BufferedInputStream(url.openStream());
-
-        ByteArrayOutputStream os = new ByteArrayOutputStream();
-
-        byte[] buffer = new byte[10000];
-
-        while (true)
-        {
-            int length = is.read(buffer);
-
-            if (length < 0) break;
-
-            os.write(buffer, 0, length);
-        }
-
-        os.close();
-        is.close();
-
-        return os.toByteArray();
-    }
-
-    private void test_loop_inside_form(String linkLabel)
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=" + linkLabel);
-        clickAndWait("reset");
-
-        assertFieldValue("title", "End World Hunger");
-        assertFieldValue("title_0", "Develop Faster-Than-Light Travel");
-        assertFieldValue("title_1", "Cure Common Cold");
-
-        type("title", "End World Hunger - today");
-        type("title_0", "Develop Faster-Than-Light Travel - immediately");
-        type("title_1", "Cure Common Cold - post haste");
-
-        clickAndWait("//input[@value='Update ToDos']");
-
-        assertFieldValue("title", "End World Hunger - today");
-        assertFieldValue("title_0", "Develop Faster-Than-Light Travel - immediately");
-        assertFieldValue("title_1", "Cure Common Cold - post haste");
-
-        clickAndWait("addNew");
-
-        type("title_2", "Conquer World");
-
-        clickAndWait("//input[@value='Update ToDos']");
-
-        assertFieldValue("title", "End World Hunger - today");
-        assertFieldValue("title_0", "Develop Faster-Than-Light Travel - immediately");
-        assertFieldValue("title_1", "Cure Common Cold - post haste");
-        assertFieldValue("title_2", "Conquer World");
-    }
-
-    /**
-     * Tests the bean editor. Along the way, tests a bunch about validation, loops, blocks, and
-     * application state objects.
-     */
-    @Test
-    public void bean_editor()
-    {
-        String submitButton = "//input[@type='submit']";
-
-        open(BASE_URL);
-        clickAndWait("link=BeanEditor Demo");
-        clickAndWait("link=Clear Data");
-        clickAndWait(submitButton);
-
-        assertTextPresent(
-                "(First Name is Required)",
-                "You must provide a value for First Name.",
-                "Everyone has to have a last name!",
-                "Year of Birth requires a value of at least 1900.");
-
-        // Part of the override for the firstName property
-
-        assertText("//input[@id='firstName']/@size", "40");
-
-        // Check override of the submit label
-
-        assertText("//input[@type='submit']/@value", "Register");
-
-        type("firstName", "a");
-        type("lastName", "b");
-        type("birthYear", "");
-        select("sex", "label=Martian");
-        click("citizen");
-
-        clickAndWait(submitButton);
-
-        assertTextPresent(
-                "You must provide at least 3 characters for First Name.",
-                "You must provide at least 5 characters for Last Name.",
-                "You must provide a value for Year of Birth.");
-
-        type("firstName", "Howard");
-        type("lastName", "Lewis Ship");
-        type("birthYear", "1966");
-
-        clickAndWait(submitButton);
-
-        assertTextPresent("[Howard]", "[Lewis Ship]", "[1966]", "[MARTIAN]", "[true]");
-    }
-
-    @Test
-    public void pageloaded_lifecycle_method_invoked()
-    {
-        open(BASE_URL);
-        clickAndWait("link=PageLoaded Demo");
-
-        assertTextPresent("[pageLoaded() was invoked.]");
-    }
-
-    /**
-     * Basic Grid rendering, with a column render override. Also tests sorting.
-     */
-    @Test
-    public void basic_grid()
-    {
-        open(BASE_URL);
-        clickAndWait("link=Grid Demo");
-
-        assertTextSeries("//th[%d]", 1, "Title", "Album", "Artist", "Genre", "Play Count", "Rating");
-
-        // Strange: I thought tr[1] was the header row ???
-
-        assertTextSeries(
-                "//tr[1]/td[%d]",
-                1,
-                "Bug Juice",
-                "Late Lounge (2 of 2)",
-                "45 Dip",
-                "Electronica",
-                "4",
-                "-");
-
-        // Here were checking that the page splits are correct
-
-        clickAndWait("link=3");
-
-        // Last on page 3:
-        assertText("//tr[25]/td[1]", "Blood Red River");
-
-        clickAndWait("link=4");
-        assertText("//tr[1]/td[1]", "Devil Song");
-
-        clickAndWait("link=7");
-        clickAndWait("link=10");
-
-        // Here's one with a customized rating cell
-
-        assertTextSeries(
-                "//tr[25]/td[%d]",
-                1,
-                "Smoked",
-                "London (Original Motion Picture Soundtrack)",
-                "The Crystal Method",
-                "Soundtrack",
-                "30",
-                "****");
-
-        clickAndWait("link=69");
-
-        assertText("//tr[22]/td[1]", "radioioAmbient");
-
-        // Sort ascending (and we're on the last page, with the highest ratings).
-
-        clickAndWait("link=Rating");
-
-        assertText(
-                "//img[@id='rating:sort']/@src",
-                "/assets/tapestry/corelib/components/sort-asc.png");
-        assertText("//img[@id='rating:sort']/@alt", "[Asc]");
-
-        assertTextSeries(
-                "//tr[22]/td[%d]",
-                1,
-                "Mona Lisa Overdrive",
-                "Labyrinth",
-                "Juno Reactor",
-                "Dance",
-                "31",
-                "*****");
-
-        // Toggle to sort descending
-
-        clickAndWait("link=Rating");
-
-        assertText(
-                "//img[@id='rating:sort']/@src",
-                "/assets/tapestry/corelib/components/sort-desc.png");
-        assertText("//img[@id='rating:sort']/@alt", "[Desc]");
-
-        assertTextSeries("//tr[1]/td[%d]", 1, "Hey Blondie", "Out from Out Where");
-
-        clickAndWait("link=Title");
-
-        assertText(
-                "//img[@id='title:sort']/@src",
-                "/assets/tapestry/corelib/components/sort-asc.png");
-        assertText("//img[@id='title:sort']/@alt", "[Asc]");
-
-        clickAndWait("link=1");
-
-        assertText("//tr[1]/td[1]", "(untitled hidden track)");
-    }
-
-    @Test
-    public void grid_from_explicit_interface_model()
-    {
-        open(BASE_URL);
-        clickAndWait("link=SimpleTrack Grid Demo");
-
-        assertTextSeries("//th[%d]", 1, "Title", "Album", "Rating");
-
-        assertTextSeries("//tr[1]/td[%d]", 1, "Bug Juice", "Late Lounge (2 of 2)", "-");
-    }
-
-    @Test
-    public void grid_enum_display()
-    {
-        open(BASE_URL);
-        clickAndWait("link=Grid Enum Demo");
-        clickAndWait("link=reset");
-
-        assertTextSeries("//tr[1]/td[%d]", 2, "End World Hunger", "Medium");
-        assertTextSeries("//tr[2]/td[%d]", 2, "Develop Faster-Than-Light Travel", "High");
-        assertTextSeries("//tr[3]/td[%d]", 2, "Cure Common Cold", "Low");
-    }
-
-    @Test
-    public void null_grid() throws Exception
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Null Grid");
-
-        assertTextPresent("There is no data to display.");
-    }
-
-    @Test
-    public void navigation_response_from_page_activate() throws Exception
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Protected Page");
-
-        assertText("//h1", "Security Alert");
-
-        // The message is set by Protected, but is rendered by SecurityAlert.
-
-        assertTextPresent("Access to Protected page is denied");
-    }
-
-    @Test
-    public void mixed_page_activation_context_and_component_context()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Kicker");
-
-        clickAndWait("actionlink");
-
-        assertTextSeries("//li[%d]", 1, "betty", "wilma");
-        assertTextPresent("No component context.");
-
-        clickAndWait("link=go");
-
-        assertTextSeries("//li[%d]", 1, "betty", "wilma");
-        assertTextSeries("//ul[2]/li[%d]", 1, "fred", "barney", "clark kent");
+
+        assertTextPresent("before it is defined: [Block wilma].");
+    }
+
+    @Test
+    public void component_parameter_default_from_method() throws Exception
+    {
+        open(BASE_URL);
+        clickAndWait("link=ParameterDefault");
+
+        assertTextPresent("Echo component default: [ParameterDefault:echo]");
+    }
+
+    @Test
+    public void embedded_components()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Countdown Page");
+
+        assertTextPresent("regexp:\\s+5\\s+4\\s+3\\s+2\\s+1\\s+");
+
+        assertTextPresent("Brought to you by the org.apache.tapestry.integration.app1.components.Count");
+    }
+
+    @Test
+    public void encoded_loop_inside_a_form()
+    {
+        test_loop_inside_form("ToDo List");
     }
-    
+
+    @Test
+    public void environmental()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Environmental Annotation Useage");
+
+        assertSourcePresent("[<strong>A message provided by the RenderableProvider component.</strong>]");
+    }
+
+    @Test
+    public void exception_report()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=BadTemplate Page");
+
+        assertTextPresent(
+                "org.apache.tapestry.ioc.internal.util.TapestryException",
+                "Failure parsing template classpath:org/apache/tapestry/integration/app1/pages/BadTemplate.html, line 7, column 15",
+                "<t:foobar>content from template</t:foobar>",
+                "Element <t:foobar> is in the Tapestry namespace, but is not a recognized Tapestry template element.");
+    }
+
+    @Test
+    public void expansion()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Expansion Page");
+
+        assertTextPresent("[value provided by a template expansion]");
+    }
+
+    /**
+     * {@link InjectComponentWorker} is largely tested by the forms tests ({@link RenderDisabled}
+     * is built on it). This test is for the failure case, where a mixin class is used with the
+     * wrong type of component.
+     */
+    @Test
+    public void inject_component_failure() throws Exception
+    {
+        open(BASE_URL);
+        clickAndWait("link=InjectComponentMismatch");
+
+        // And exception message:
+
+        assertTextPresent("Component InjectComponentMismatch is not assignable to field org.apache.tapestry.corelib.mixins.RenderDisabled._field (of type org.apache.tapestry.Field).");
+    }
+
+    @Test
+    public void injection() throws Exception
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Inject Demo");
+
+        // This is a test for a named @Inject:
+        assertTextPresent("<Proxy for Request(org.apache.tapestry.services.Request)>");
+
+        // This is a test for an anonymous @Inject and ComponentResourcesInjectionProvider
+        assertTextPresent("ComponentResources[InjectDemo]");
+
+        // Another test, DefaultInjectionProvider
+        assertTextPresent("<Proxy for BindingSource(org.apache.tapestry.services.BindingSource)>");
+    }
+
+    @Test
+    public void instance_mixin()
+    {
+        open(BASE_URL);
+
+        final String[] dates =
+        { "Jun 13, 1999", "Jul 15, 2001", "Dec 4, 2005" };
+
+        clickAndWait("link=InstanceMixin");
+
+        for (String date : dates)
+        {
+            String snippet = String.format("[%s]", date);
+
+            assertSourcePresent(snippet);
+        }
+
+        clickAndWait("link=Toggle emphasis");
+
+        for (String date : dates)
+        {
+            String snippet = String.format("[<em>%s</em>]", date);
+            assertSourcePresent(snippet);
+        }
+    }
+
+    @Test
+    public void localization()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Localization");
+
+        assertTextPresent("Via injected Messages property: [Accessed via injected Messages]");
+        assertTextPresent("Via message: binding prefix: [Accessed via message: binding prefix]");
+        assertTextPresent("From Application Message Catalog: [Application Catalog Working]");
+        assertTextPresent("Page locale: [en]");
+        clickAndWait("link=French");
+        assertTextPresent("Page locale: [fr]");
+        clickAndWait("link=English");
+        assertTextPresent("Page locale: [en]");
+    }
+
+    @Test
+    public void page_injection() throws Exception
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Inject Demo");
+
+        clickAndWait("link=Fred");
+
+        assertTextPresent("You clicked Fred.");
+
+        clickAndWait("link=Back");
+        clickAndWait("link=Barney");
+
+        assertTextPresent("You clicked Barney.");
+
+        clickAndWait("link=Back");
+        clickAndWait("link=Wilma");
+        assertTextPresent("You clicked Wilma.");
+    }
+
+    @Test
+    public void passivate_activate() throws Exception
+    {
+        open(BASE_URL);
+        clickAndWait("link=NumberSelect");
+        clickAndWait("link=5");
+        assertTextPresent("You chose 5.");
+    }
+
+    @Test
+    public void password_field()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=PasswordFieldDemo");
+
+        type("userName", "howard");
+        type("password", "wrong-password");
+
+        clickAndWait("//input[@type='submit']");
+
+        assertFieldValue("userName", "howard");
+        // Verify that password fields do not render a non-blank password, even when it is known.
+        assertFieldValue("password", "");
+
+        assertTextPresent("[howard]");
+        assertTextPresent("[wrong-password]");
+
+        type("password", "tapestry");
+
+        clickAndWait("//input[@type='submit']");
+
+        assertTextPresent("You have provided the correct user name and password.");
+    }
+
+    @Test
+    public void render_phase_method_returns_a_component() throws Exception
+    {
+        open(BASE_URL);
+        clickAndWait("link=RenderComponentDemo");
+
+        assertText("//span[@id='container']", "[]");
+
+        // Sneak in a little test for If and parameter else:
+
+        assertTextPresent("Should be blank:");
+
+        clickAndWait("enabled");
+
+        // After clicking the link (which submits the form), the page re-renders and shows us
+        // the optional component from inside the NeverRender, resurrected to render on the page
+        // after all.
+
+        assertText("//span[@id='container']/span", "Optional Text");
+
+        assertTextPresent("Should now show up:");
+    }
+
+    @Test
+    public void render_phase_order()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=RenderPhaseOrder");
+
+        assertTextPresent("[BEGIN-TRACER-MIXIN BEGIN-ABSTRACT-TRACER BEGIN-TRACER BODY AFTER-TRACER AFTER-ABSTRACT-TRACER AFTER-TRACER-MIXIN]");
+    }
+
+    @Test
+    public void server_side_validation_for_textfield_and_textarea() throws Exception
+    {
+        open(BASE_URL);
+        clickAndWait("link=ValidForm");
+        clickAndWait("//input[@type='submit']");
+        assertTextPresent("You must provide a value for Email.");
+        // This is an overrdden validation error message:
+        assertTextPresent("Please provide a detailed description of the incident.");
+
+        // Check on decorations via the default validation decorator:
+
+        assertText("//label[1]/@class", "t-error");
+        assertText("//label[2]/@class", "t-error");
+        assertText("//input[@id='email']/@class", "t-error");
+        assertText("//textarea[@id='message']/@class", "t-error");
+
+        type("email", "foo@bar.baz");
+        type("message", "Show me the money!");
+        type("hours", "foo");
+
+        clickAndWait("//input[@type='submit']");
+
+        assertTextPresent("[false]");
+        assertTextPresent("The input value 'foo' is not parseable as an integer value.");
+
+        assertText("//input[@id='hours']/@value", "foo");
+
+        type("hours", " 19 ");
+        click("//input[@id='urgent']");
+        clickAndWait("//input[@type='submit']");
+
+        // Make sure the decoration went away.
+
+        // Sorry, not sure how to do that, since the attributes don't exist, we get xpath errors.
+
+        // assertText("//label[1]/@class", "");
+        // assertText("//label[2]/@class", "");
+        // assertText("//input[@id='email']/@class", "");
+        // assertText("//textarea[@id='message']/@class", "");
+
+        assertTextPresent("[foo@bar.baz]");
+        assertTextPresent("[Show me the money!]");
+        assertTextPresent("[true]");
+        assertTextPresent("[19]");
+    }
+
+    @Test
+    public void simple_component_event()
+    {
+        final String YOU_CHOSE = "You chose: ";
+
+        open(BASE_URL);
+
+        clickAndWait("link=Action Page");
+
+        assertFalse(isTextPresent(YOU_CHOSE));
+
+        for (int i = 2; i < 5; i++)
+        {
+            clickAndWait("link=" + i);
+
+            assertTextPresent(YOU_CHOSE + i);
+        }
+    }
+
+    /**
+     * Tests for forms and form submissions and basic form control components. This also tests a few
+     * other things, such as computed default bindings and invisible instrumentation.
+     */
+    @Test
+    public void simple_form()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=SimpleForm");
+
+        assertText("//label[@id='disabled:label']", "Disabled");
+        assertText("//label[@id='email:label']", "Email");
+        assertText("//label[@id='message:label']", "Incident Message");
+        assertText("//label[@id='operatingSystem:label']", "Operating System");
+        assertText("//label[@id='department:label']", "Department");
+        assertText("//label[@id='urgent:label']", "Urgent Processing Requested");
+
+        assertFieldValue("email", "");
+        assertFieldValue("message", "");
+        assertFieldValue("operatingSystem", "osx");
+        assertFieldValue("department", "ACCOUNTING");
+        assertFieldValue("urgent", "on");
+
+        type("email", "foo@bar.baz");
+        type("message", "Message for you, sir!");
+        select("operatingSystem", "Windows NT");
+        select("department", "R&D");
+        click("urgent");
+
+        clickAndWait("//input[@type='submit']");
+
+        assertFieldValue("email", "foo@bar.baz");
+        assertFieldValue("message", "Message for you, sir!");
+        assertFieldValue("urgent", "off");
+
+        // Tried to use "email:" and "exact:email:" but Selenium 0.8.1 doesn't seem to accept that.
+
+        assertTextPresent(
+                "[foo@bar.baz]",
+                "[Message for you, sir!]",
+                "[false]",
+                "[winnt]",
+                "[RESEARCH_AND_DESIGN]");
+
+        // Haven't figured out how to get selenium to check that fields are disabled.
+    }
+
+    @Test
+    public void subclass_inherits_parent_template()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=ExpansionSubclass");
+
+        assertTextPresent("[value provided, in the subclass, via a template expansion]");
+    }
+
+    @Test
+    public void template_overridden()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Template Overriden by Class Page");
+
+        assertTextPresent("Output: ClassValue");
+    }
+
+    @Test
+    public void volatile_loop_inside_a_form()
+    {
+        test_loop_inside_form("ToDo List (Volatile)");
+    }
+
+    /** This also verifies the use of meta data to set the default strategy. */
+    @Test
+    public void flash_persistence()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=FlashDemo");
+
+        assertTextPresent("[]");
+
+        clickAndWait("show");
+
+        assertTextPresent("[You clicked the link!]");
+
+        clickAndWait("refresh");
+
+        assertTextPresent("[]");
+    }
+
+    private byte[] readContent(URL url) throws Exception
+    {
+        InputStream is = new BufferedInputStream(url.openStream());
+
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+        byte[] buffer = new byte[10000];
+
+        while (true)
+        {
+            int length = is.read(buffer);
+
+            if (length < 0) break;
+
+            os.write(buffer, 0, length);
+        }
+
+        os.close();
+        is.close();
+
+        return os.toByteArray();
+    }
+
+    private void test_loop_inside_form(String linkLabel)
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=" + linkLabel);
+        clickAndWait("reset");
+
+        assertFieldValue("title", "End World Hunger");
+        assertFieldValue("title_0", "Develop Faster-Than-Light Travel");
+        assertFieldValue("title_1", "Cure Common Cold");
+
+        type("title", "End World Hunger - today");
+        type("title_0", "Develop Faster-Than-Light Travel - immediately");
+        type("title_1", "Cure Common Cold - post haste");
+
+        clickAndWait("//input[@value='Update ToDos']");
+
+        assertFieldValue("title", "End World Hunger - today");
+        assertFieldValue("title_0", "Develop Faster-Than-Light Travel - immediately");
+        assertFieldValue("title_1", "Cure Common Cold - post haste");
+
+        clickAndWait("addNew");
+
+        type("title_2", "Conquer World");
+
+        clickAndWait("//input[@value='Update ToDos']");
+
+        assertFieldValue("title", "End World Hunger - today");
+        assertFieldValue("title_0", "Develop Faster-Than-Light Travel - immediately");
+        assertFieldValue("title_1", "Cure Common Cold - post haste");
+        assertFieldValue("title_2", "Conquer World");
+    }
+
+    /**
+     * Tests the bean editor. Along the way, tests a bunch about validation, loops, blocks, and
+     * application state objects.
+     */
+    @Test
+    public void bean_editor()
+    {
+        String submitButton = "//input[@type='submit']";
+
+        open(BASE_URL);
+        clickAndWait("link=BeanEditor Demo");
+        clickAndWait("link=Clear Data");
+        clickAndWait(submitButton);
+
+        assertTextPresent(
+                "(First Name is Required)",
+                "You must provide a value for First Name.",
+                "Everyone has to have a last name!",
+                "Year of Birth requires a value of at least 1900.");
+
+        // Part of the override for the firstName property
+
+        assertText("//input[@id='firstName']/@size", "40");
+
+        // Check override of the submit label
+
+        assertText("//input[@type='submit']/@value", "Register");
+
+        type("firstName", "a");
+        type("lastName", "b");
+        type("birthYear", "");
+        select("sex", "label=Martian");
+        click("citizen");
+
+        clickAndWait(submitButton);
+
+        assertTextPresent(
+                "You must provide at least 3 characters for First Name.",
+                "You must provide at least 5 characters for Last Name.",
+                "You must provide a value for Year of Birth.");
+
+        type("firstName", "Howard");
+        type("lastName", "Lewis Ship");
+        type("birthYear", "1966");
+
+        clickAndWait(submitButton);
+
+        assertTextPresent("[Howard]", "[Lewis Ship]", "[1966]", "[MARTIAN]", "[true]");
+    }
+
+    @Test
+    public void pageloaded_lifecycle_method_invoked()
+    {
+        open(BASE_URL);
+        clickAndWait("link=PageLoaded Demo");
+
+        assertTextPresent("[pageLoaded() was invoked.]");
+    }
+
+    /**
+     * Basic Grid rendering, with a column render override. Also tests sorting.
+     */
+    @Test
+    public void basic_grid()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Grid Demo");
+
+        assertTextSeries("//th[%d]", 1, "Title", "Album", "Artist", "Genre", "Play Count", "Rating");
+
+        // Strange: I thought tr[1] was the header row ???
+
+        assertTextSeries(
+                "//tr[1]/td[%d]",
+                1,
+                "Bug Juice",
+                "Late Lounge (2 of 2)",
+                "45 Dip",
+                "Electronica",
+                "4",
+                "-");
+
+        // Here were checking that the page splits are correct
+
+        clickAndWait("link=3");
+
+        // Last on page 3:
+        assertText("//tr[25]/td[1]", "Blood Red River");
+
+        clickAndWait("link=4");
+        assertText("//tr[1]/td[1]", "Devil Song");
+
+        clickAndWait("link=7");
+        clickAndWait("link=10");
+
+        // Here's one with a customized rating cell
+
+        assertTextSeries(
+                "//tr[25]/td[%d]",
+                1,
+                "Smoked",
+                "London (Original Motion Picture Soundtrack)",
+                "The Crystal Method",
+                "Soundtrack",
+                "30",
+                "****");
+
+        clickAndWait("link=69");
+
+        assertText("//tr[22]/td[1]", "radioioAmbient");
+
+        // Sort ascending (and we're on the last page, with the highest ratings).
+
+        clickAndWait("link=Rating");
+
+        assertText(
+                "//img[@id='rating:sort']/@src",
+                "/assets/tapestry/corelib/components/sort-asc.png");
+        assertText("//img[@id='rating:sort']/@alt", "[Asc]");
+
+        assertTextSeries(
+                "//tr[22]/td[%d]",
+                1,
+                "Mona Lisa Overdrive",
+                "Labyrinth",
+                "Juno Reactor",
+                "Dance",
+                "31",
+                "*****");
+
+        // Toggle to sort descending
+
+        clickAndWait("link=Rating");
+
+        assertText(
+                "//img[@id='rating:sort']/@src",
+                "/assets/tapestry/corelib/components/sort-desc.png");
+        assertText("//img[@id='rating:sort']/@alt", "[Desc]");
+
+        assertTextSeries("//tr[1]/td[%d]", 1, "Hey Blondie", "Out from Out Where");
+
+        clickAndWait("link=Title");
+
+        assertText(
+                "//img[@id='title:sort']/@src",
+                "/assets/tapestry/corelib/components/sort-asc.png");
+        assertText("//img[@id='title:sort']/@alt", "[Asc]");
+
+        clickAndWait("link=1");
+
+        assertText("//tr[1]/td[1]", "(untitled hidden track)");
+    }
+
+    @Test
+    public void grid_from_explicit_interface_model()
+    {
+        open(BASE_URL);
+        clickAndWait("link=SimpleTrack Grid Demo");
+
+        assertTextSeries("//th[%d]", 1, "Title", "Album", "Rating");
+
+        assertTextSeries("//tr[1]/td[%d]", 1, "Bug Juice", "Late Lounge (2 of 2)", "-");
+    }
+
+    @Test
+    public void grid_enum_display()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Grid Enum Demo");
+        clickAndWait("link=reset");
+
+        assertTextSeries("//tr[1]/td[%d]", 2, "End World Hunger", "Medium");
+        assertTextSeries("//tr[2]/td[%d]", 2, "Develop Faster-Than-Light Travel", "High");
+        assertTextSeries("//tr[3]/td[%d]", 2, "Cure Common Cold", "Low");
+    }
+
+    @Test
+    public void null_grid() throws Exception
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Null Grid");
+
+        assertTextPresent("There is no data to display.");
+    }
+
+    @Test
+    public void navigation_response_from_page_activate() throws Exception
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Protected Page");
+
+        assertText("//h1", "Security Alert");
+
+        // The message is set by Protected, but is rendered by SecurityAlert.
+
+        assertTextPresent("Access to Protected page is denied");
+    }
+
+    @Test
+    public void mixed_page_activation_context_and_component_context()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Kicker");
+
+        clickAndWait("actionlink");
+
+        assertTextSeries("//li[%d]", 1, "betty", "wilma");
+        assertTextPresent("No component context.");
+
+        clickAndWait("link=go");
+
+        assertTextSeries("//li[%d]", 1, "betty", "wilma");
+        assertTextSeries("//ul[2]/li[%d]", 1, "fred", "barney", "clark kent");
+    }
+
     @Test
     public void page_link_with_explicit_empty_context()
     {
@@ -774,306 +774,334 @@
         clickAndWait("link=Kicker");
 
         clickAndWait("actionlink");
-   
+
         assertTextSeries("//li[%d]", 1, "betty", "wilma");
-        
+
         clickAndWait("nocontext");
-        
+
+        assertTextPresent("No activation context.");
+    }
+
+    @Test
+    public void page_link_with_explicit_activation_context()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=PageLink Context Demo");
+
+        clickAndWait("link=no context");
+
         assertTextPresent("No activation context.");
-    }
-
-    @Test
-    public void page_link_with_explicit_activation_context()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=PageLink Context Demo");
-
-        clickAndWait("link=no context");
-
-        assertTextPresent("No activation context.");
-
-        clickAndWait("link=PageLink Context Demo");
-
-        clickAndWait("link=literal context");
-
-        assertText("//li[1]", "literal context");
-
-        clickAndWait("link=PageLink Context Demo");
-
-        clickAndWait("link=computed context");
-
-        assertTextSeries("//li[%d]", 1, "fred", "7", "true");
-    }
-
-    @Test
-    public void client_side_validation()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Client Validation Demo");
-
-        assertTextSeries(
-                "//script[%d]/@src",
-                1,
-                "/assets/scriptaculous/prototype.js",
-                "/assets/scriptaculous/scriptaculous.js");
-
-        clickAndWait("link=Clear Data");
-
-        // Notice: click, not click and wait.
-
-        click("//input[@type='submit']");
-
-        assertTextSeries(
-                "//li[%d]",
-                1,
-                "You must provide a value for First Name.",
-                "Everyone has to have a last name!",
-                "Year of Birth requires a value of at least 1900.");
-
-        type("firstName", "Howard");
-        type("lastName", "Lewis Ship");
-        type("birthYear", "1000");
-        click("//input[@type='submit']");
-
-        assertText("//li", "Year of Birth requires a value of at least 1900.");
-
-        type("birthYear", "1966");
-        click("citizen");
-
-        clickAndWait("//input[@type='submit']");
-
-        assertTextPresent("First Name: [Howard]");
-    }
-
-    @Test
-    public void recursive_components_are_identified_as_errors()
-    {
-        open(BASE_URL);
-        clickAndWait("link=Recursive Demo");
-
-        assertTextPresent(
-                "An unexpected application exception has occurred.",
-                "The template for component org.apache.tapestry.integration.app1.components.Recursive is recursive (contains another direct or indirect reference to component org.apache.tapestry.integration.app1.components.Recursive). This is not supported (components may not contain themselves).",
-                "This component is <t:recursive>recursive</t:recursive>, so we\'ll see a failure.");
-    }
-
-    @Test
-    public void render_phase_method_may_return_renderable()
-    {
-        open(BASE_URL);
-        clickAndWait("link=Renderable Demo");
-
-        assertTextPresent("Renderable Demo", "[This proves it works.]");
-    }
-
-    @Test
-    public void verify_event_handler_invocation_order_and_circumstance()
-    {
-        String clear = "link=clear";
-
-        open(BASE_URL);
-        clickAndWait("link=EventHandler Demo");
-        clickAndWait(clear);
-
-        clickAndWait("wilma");
-        assertTextPresent("[parent.eventHandlerZero(), parent.onAction(), child.eventHandlerZeroChild(), child.onAction()]");
-
-        clickAndWait(clear);
-        clickAndWait("barney");
-
-        assertTextPresent("[parent.eventHandlerOne(String), parent.eventHandlerZero(), parent.onAction(), parent.onAction(String), child.eventHandlerOneChild(), child.eventHandlerZeroChild(), child.onAction(), child.onAction(String)]");
-
-        clickAndWait(clear);
-        clickAndWait("betty");
-        assertTextPresent("[parent.eventHandlerOne(String), parent.eventHandlerZero(), parent.onAction(), parent.onAction(String), child.eventHandlerOneChild(), child.eventHandlerZeroChild(), child.onAction(), child.onAction(String)]");
-
-        clickAndWait(clear);
-        clickAndWait("fred");
-
-        assertTextPresent("[parent.eventHandlerOne(String), parent.eventHandlerZero(), parent.onAction(), parent.onAction(String), child.eventHandlerForFred(), child.eventHandlerOneChild(), child.eventHandlerZeroChild(), child.onAction(), child.onAction(String), child.onActionFromFred(), child.onActionFromFred(String), child.onAnyEventFromFred(), child.onAnyEventFromFred(String)]");
-    }
-
-    @Test
-    public void inherited_bindings()
-    {
-        open(BASE_URL);
-        clickAndWait("link=Inherited Bindings Demo");
-
-        assertTextPresent(
-                "Bound: [ value: the-bound-value, bound: true ]",
-                "Unbound: [ value: null, bound: false ]");
-    }
-
-    @Test
-    public void client_persistence()
-    {
-        open(BASE_URL);
-        clickAndWait("link=Client Persistence Demo");
-
-        assertTextPresent("Persisted value: []", "Session: [false]");
-
-        clickAndWait("link=store string");
-
-        assertTextPresent("Persisted value: [A String]", "Session: [false]");
-    }
-
-    @Test
-    public void attribute_expansions()
-    {
-        open(BASE_URL);
-        clickAndWait("link=Attribute Expansions Demo");
-
-        assertText("//div[@id='mixed-expansion']/@style", "color: blue;");
-        assertText("//div[@id='single']/@class", "red");
-        assertText("//div[@id='consecutive']/@class", "goober-red");
-        assertText("//div[@id='trailer']/@class", "goober-green");
-        assertText(
-                "//div[@id='formal']/text()",
-                "ALERT-expansions work inside formal component parameters as well");
-
-        // An unrelated test, but fills in a bunch of minor gaps.
-
-        assertSourcePresent("<!-- A comment! -->");
-    }
-
-    @Test
-    public void palette_component()
-    {
-        open(BASE_URL);
-        clickAndWait("link=Palette Demo");
-        clickAndWait("link=reset");
-
-        addSelection("languages:avail", "label=Haskell");
-        addSelection("languages:avail", "label=Javascript");
-        click("languages:select");
-
-        clickAndWait("//input[@type='submit']");
-        assertTextPresent("Selected Languages: [HASKELL, JAVASCRIPT]");
-
-        addSelection("languages", "label=Javascript");
-        click("languages:deselect");
-
-        addSelection("languages:avail", "label=Perl");
-        removeSelection("languages:avail", "label=Javascript");
-        addSelection("languages:avail", "label=Erlang");
-        addSelection("languages:avail", "label=Java");
-        addSelection("languages:avail", "label=Lisp");
-        addSelection("languages:avail", "label=Ml");
-        addSelection("languages:avail", "label=Python");
-        addSelection("languages:avail", "label=Ruby");
-
-        click("languages:select");
-
-        clickAndWait("//input[@type='submit']");
-
-        assertTextPresent("[ERLANG, HASKELL, JAVA, LISP, ML, PERL, PYTHON, RUBY]");
-
-        check("reorder");
-        clickAndWait("//input[@type='submit']");
-
-        addSelection("languages", "label=Ruby");
-
-        for (int i = 0; i < 6; i++)
-            click("languages:up");
-
-        removeSelection("languages", "label=Ruby");
-        addSelection("languages", "label=Perl");
-
-        click("languages:down");
-
-        clickAndWait("//input[@type='submit']");
-
-        assertTextPresent("[ERLANG, RUBY, HASKELL, JAVA, LISP, ML, PYTHON, PERL]");
-    }
-
-    @Test
-    public void event_handler_return_types()
-    {
-
-        open(BASE_URL);
-        assertTextPresent("Tapestry 5 Integration Application 1");
-
-        clickAndWait("link=Return Types");
-        assertTextPresent("Return Type Tests");
-
-        clickAndWait("link=null");
-        assertTextPresent("Return Type Tests");
-
-        clickAndWait("link=string");
-        assertTextPresent("Tapestry 5 Integration Application 1");
-        goBack();
-        waitForPageToLoad();
-
-        clickAndWait("link=class");
-        assertTextPresent("Tapestry 5 Integration Application 1");
-        goBack();
-        waitForPageToLoad();
-
-        clickAndWait("link=page");
-        assertTextPresent("Tapestry 5 Integration Application 1");
-        goBack();
-        waitForPageToLoad();
-
-        clickAndWait("link=link");
-        assertTextPresent("Tapestry 5 Integration Application 1");
-        goBack();
-        waitForPageToLoad();
-
-        clickAndWait("link=stream");
-        assertTextPresent("Success!");
-        goBack();
-        waitForPageToLoad();
-
-        clickAndWait("link=bad");
-        assertTextPresent(
-                "An unexpected application exception has occurred.",
-                "An event handler for component org.apache.tapestry.integration.app1.pages.Start returned the value 20 (from method org.apache.tapestry.integration.app1.pages.Start.onActionFromBadReturnType() (at Start.java:34)). Return type java.lang.Integer can not be handled.");
-
-    }
-
-    @Test
-    public void access_to_page_name()
-    {
-        open(BASE_URL);
-
-        assertTextPresent("Currently on page: Start");
-
-        clickAndWait("link=Grid Demo");
-
-        assertTextPresent("Currently on page: GridDemo");
-    }
-
-    @Test
-    public void form_encoding_type()
-    {
-        open(BASE_URL);
-
-        clickAndWait("link=Form Encoding Type");
-
-        assertText("//form/@enctype", "x-override");
-    }
-
-    @Test
-    public void radio_button_and_group()
-    {
-        open(BASE_URL);
-
-        String update = "//input[@type='submit']";
-
-        clickAndWait("link=RadioDemo");
-
-        click("//label[.='Accounting']");
-
-        clickAndWait(update);
-
-        assertTextPresent("Selected department: ACCOUNTING");
-
-        click("//label[.='Sales And Marketing']");
-
-        clickAndWait(update);
-
-        assertTextPresent("Selected department: SALES_AND_MARKETING");
-    }
-}
+
+        clickAndWait("link=PageLink Context Demo");
+
+        clickAndWait("link=literal context");
+
+        assertText("//li[1]", "literal context");
+
+        clickAndWait("link=PageLink Context Demo");
+
+        clickAndWait("link=computed context");
+
+        assertTextSeries("//li[%d]", 1, "fred", "7", "true");
+    }
+
+    @Test
+    public void client_side_validation()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Client Validation Demo");
+
+        assertTextSeries(
+                "//script[%d]/@src",
+                1,
+                "/assets/scriptaculous/prototype.js",
+                "/assets/scriptaculous/scriptaculous.js");
+
+        clickAndWait("link=Clear Data");
+
+        // Notice: click, not click and wait.
+
+        click("//input[@type='submit']");
+
+        assertTextSeries(
+                "//li[%d]",
+                1,
+                "You must provide a value for First Name.",
+                "Everyone has to have a last name!",
+                "Year of Birth requires a value of at least 1900.");
+
+        type("firstName", "Howard");
+        type("lastName", "Lewis Ship");
+        type("birthYear", "1000");
+        click("//input[@type='submit']");
+
+        assertText("//li", "Year of Birth requires a value of at least 1900.");
+
+        type("birthYear", "1966");
+        click("citizen");
+
+        clickAndWait("//input[@type='submit']");
+
+        assertTextPresent("First Name: [Howard]");
+    }
+
+    @Test
+    public void recursive_components_are_identified_as_errors()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Recursive Demo");
+
+        assertTextPresent(
+                "An unexpected application exception has occurred.",
+                "The template for component org.apache.tapestry.integration.app1.components.Recursive is recursive (contains another direct or indirect reference to component org.apache.tapestry.integration.app1.components.Recursive). This is not supported (components may not contain themselves).",
+                "This component is <t:recursive>recursive</t:recursive>, so we\'ll see a failure.");
+    }
+
+    @Test
+    public void render_phase_method_may_return_renderable()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Renderable Demo");
+
+        assertTextPresent("Renderable Demo", "[This proves it works.]");
+    }
+
+    @Test
+    public void verify_event_handler_invocation_order_and_circumstance()
+    {
+        String clear = "link=clear";
+
+        open(BASE_URL);
+        clickAndWait("link=EventHandler Demo");
+        clickAndWait(clear);
+
+        clickAndWait("wilma");
+        assertTextPresent("[parent.eventHandlerZero(), parent.onAction(), child.eventHandlerZeroChild(), child.onAction()]");
+
+        clickAndWait(clear);
+        clickAndWait("barney");
+
+        assertTextPresent("[parent.eventHandlerOne(String), parent.eventHandlerZero(), parent.onAction(), parent.onAction(String), child.eventHandlerOneChild(), child.eventHandlerZeroChild(), child.onAction(), child.onAction(String)]");
+
+        clickAndWait(clear);
+        clickAndWait("betty");
+        assertTextPresent("[parent.eventHandlerOne(String), parent.eventHandlerZero(), parent.onAction(), parent.onAction(String), child.eventHandlerOneChild(), child.eventHandlerZeroChild(), child.onAction(), child.onAction(String)]");
+
+        clickAndWait(clear);
+        clickAndWait("fred");
+
+        assertTextPresent("[parent.eventHandlerOne(String), parent.eventHandlerZero(), parent.onAction(), parent.onAction(String), child.eventHandlerForFred(), child.eventHandlerOneChild(), child.eventHandlerZeroChild(), child.onAction(), child.onAction(String), child.onActionFromFred(), child.onActionFromFred(String), child.onAnyEventFromFred(), child.onAnyEventFromFred(String)]");
+    }
+
+    @Test
+    public void inherited_bindings()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Inherited Bindings Demo");
+
+        assertTextPresent(
+                "Bound: [ value: the-bound-value, bound: true ]",
+                "Unbound: [ value: null, bound: false ]");
+    }
+
+    @Test
+    public void client_persistence()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Client Persistence Demo");
+
+        assertTextPresent("Persisted value: []", "Session: [false]");
+
+        clickAndWait("link=store string");
+
+        assertTextPresent("Persisted value: [A String]", "Session: [false]");
+    }
+
+    @Test
+    public void attribute_expansions()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Attribute Expansions Demo");
+
+        assertText("//div[@id='mixed-expansion']/@style", "color: blue;");
+        assertText("//div[@id='single']/@class", "red");
+        assertText("//div[@id='consecutive']/@class", "goober-red");
+        assertText("//div[@id='trailer']/@class", "goober-green");
+        assertText(
+                "//div[@id='formal']/text()",
+                "ALERT-expansions work inside formal component parameters as well");
+
+        // An unrelated test, but fills in a bunch of minor gaps.
+
+        assertSourcePresent("<!-- A comment! -->");
+    }
+
+    @Test
+    public void palette_component()
+    {
+        open(BASE_URL);
+        clickAndWait("link=Palette Demo");
+        clickAndWait("link=reset");
+
+        addSelection("languages:avail", "label=Haskell");
+        addSelection("languages:avail", "label=Javascript");
+        click("languages:select");
+
+        clickAndWait("//input[@type='submit']");
+        assertTextPresent("Selected Languages: [HASKELL, JAVASCRIPT]");
+
+        addSelection("languages", "label=Javascript");
+        click("languages:deselect");
+
+        addSelection("languages:avail", "label=Perl");
+        removeSelection("languages:avail", "label=Javascript");
+        addSelection("languages:avail", "label=Erlang");
+        addSelection("languages:avail", "label=Java");
+        addSelection("languages:avail", "label=Lisp");
+        addSelection("languages:avail", "label=Ml");
+        addSelection("languages:avail", "label=Python");
+        addSelection("languages:avail", "label=Ruby");
+
+        click("languages:select");
+
+        clickAndWait("//input[@type='submit']");
+
+        assertTextPresent("[ERLANG, HASKELL, JAVA, LISP, ML, PERL, PYTHON, RUBY]");
+
+        check("reorder");
+        clickAndWait("//input[@type='submit']");
+
+        addSelection("languages", "label=Ruby");
+
+        for (int i = 0; i < 6; i++)
+            click("languages:up");
+
+        removeSelection("languages", "label=Ruby");
+        addSelection("languages", "label=Perl");
+
+        click("languages:down");
+
+        clickAndWait("//input[@type='submit']");
+
+        assertTextPresent("[ERLANG, RUBY, HASKELL, JAVA, LISP, ML, PYTHON, PERL]");
+    }
+
+    @Test
+    public void event_handler_return_types()
+    {
+
+        open(BASE_URL);
+        assertTextPresent("Tapestry 5 Integration Application 1");
+
+        clickAndWait("link=Return Types");
+        assertTextPresent("Return Type Tests");
+
+        clickAndWait("link=null");
+        assertTextPresent("Return Type Tests");
+
+        clickAndWait("link=string");
+        assertTextPresent("Tapestry 5 Integration Application 1");
+        goBack();
+        waitForPageToLoad();
+
+        clickAndWait("link=class");
+        assertTextPresent("Tapestry 5 Integration Application 1");
+        goBack();
+        waitForPageToLoad();
+
+        clickAndWait("link=page");
+        assertTextPresent("Tapestry 5 Integration Application 1");
+        goBack();
+        waitForPageToLoad();
+
+        clickAndWait("link=link");
+        assertTextPresent("Tapestry 5 Integration Application 1");
+        goBack();
+        waitForPageToLoad();
+
+        clickAndWait("link=stream");
+        assertTextPresent("Success!");
+        goBack();
+        waitForPageToLoad();
+
+        clickAndWait("link=bad");
+        assertTextPresent(
+                "An unexpected application exception has occurred.",
+                "An event handler for component org.apache.tapestry.integration.app1.pages.Start returned the value 20 (from method org.apache.tapestry.integration.app1.pages.Start.onActionFromBadReturnType() (at Start.java:34)). Return type java.lang.Integer can not be handled.");
+
+    }
+
+    @Test
+    public void access_to_page_name()
+    {
+        open(BASE_URL);
+
+        assertTextPresent("Currently on page: Start");
+
+        clickAndWait("link=Grid Demo");
+
+        assertTextPresent("Currently on page: GridDemo");
+    }
+
+    @Test
+    public void form_encoding_type()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Form Encoding Type");
+
+        assertText("//form/@enctype", "x-override");
+    }
+
+    @Test
+    public void radio_button_and_group()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=RadioDemo");
+
+        String update = "//input[@type='submit']";
+
+        click("//label[.='Accounting']");
+
+        clickAndWait(update);
+
+        assertTextPresent("Selected department: ACCOUNTING");
+
+        click("//label[.='Sales And Marketing']");
+
+        clickAndWait(update);
+
+        assertTextPresent("Selected department: SALES_AND_MARKETING");
+    }
+
+    @Test
+    public void regexp_validator()
+    {
+        open(BASE_URL);
+
+        clickAndWait("link=Regexp Demo");
+
+        String update = "//input[@type='submit']";
+
+        type("zipCode", "abc");
+
+        clickAndWait(update);
+
+        assertTextPresent("A zip code consists of five or nine digits, eg: 02134 or 90125-4472.");
+
+        type("zipCode", "12345");
+
+        clickAndWait(update);
+
+        assertTextPresent("Zip code: [12345]");
+
+        type("zipCode", "12345-9876");
+
+        clickAndWait(update);
+
+        assertTextPresent("Zip code: [12345-9876]");
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/RegexpDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/RegexpDemo.java?view=auto&rev=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/RegexpDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/pages/RegexpDemo.java Wed Jun 27 11:33:28 2007
@@ -0,0 +1,36 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.integration.app1.pages;
+
+import org.apache.tapestry.annotations.Persist;
+import org.apache.tapestry.beaneditor.Validate;
+
+public class RegexpDemo
+{
+    @Persist
+    private String _zipCode;
+
+    // regexp requires a constraint, that'll come from the message catalog
+    @Validate("required,regexp")
+    public String getZipCode()
+    {
+        return _zipCode;
+    }
+
+    public void setZipCode(String zipCode)
+    {
+        _zipCode = zipCode;
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java?view=diff&rev=551256&r1=551255&r2=551256
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/internal/TapestryInternalUtilsTest.java Wed Jun 27 11:33:28 2007
@@ -23,6 +23,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.regex.Pattern;
 
 import org.apache.tapestry.OptionModel;
 import org.apache.tapestry.PropertyConduit;
@@ -461,6 +462,18 @@
 
         assertFalse(TapestryInternalUtils.isEqual(left, right));
         assertTrue(TapestryInternalUtils.isEqual(left, new String(left)));
+    }
+
+    @Test
+    public void type_coersion_string_to_pattern()
+    {
+        TypeCoercer coercer = getObject(TypeCoercer.class, null);
+
+        String input = "\\s+";
+
+        Pattern pattern = coercer.coerce(input, Pattern.class);
+
+        assertEquals(pattern.toString(), input);
     }
 
 }