cmlenz      2002/11/28 02:57:40

  Modified:    framework/src/java/j2ee13/org/apache/cactus/extension/jsp
                        JspTagLifecycle.java
               sample-servlet/src/unit/j2ee13/org/apache/cactus/unit
                        TestJspTagLifecycle.java
  Log:
  - Fixed bug where the body content was popped *after* the doEndTag() method
    was called, which breaks BodyTag tests
  - Implemented interceptor chaining to allow multiple interceptors on the
    lifecycle
  - Added shortcut assertion methods based on custom interceptors:
    - assertBodyEvaluated()
    - assertBodyEvaluated(numIterations)
    - assertBodySkipped()
    - assertScopedVariableExposed(String,Object[],int)
  - Added interceptors for adding nested template text and nested tags, this
    allows testing of tag collaboration
  - Added logging at DEBUG level
  - Updated the tests to reflect the changes, and added more test cases
  
  Revision  Changes    Path
  1.3       +424 -119  
jakarta-cactus/framework/src/java/j2ee13/org/apache/cactus/extension/jsp/JspTagLifecycle.java
  
  Index: JspTagLifecycle.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-cactus/framework/src/java/j2ee13/org/apache/cactus/extension/jsp/JspTagLifecycle.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- JspTagLifecycle.java      26 Nov 2002 09:22:47 -0000      1.2
  +++ JspTagLifecycle.java      28 Nov 2002 10:57:39 -0000      1.3
  @@ -57,6 +57,11 @@
   package org.apache.cactus.extension.jsp;
   
   import java.io.IOException;
  +import java.util.ArrayList;
  +import java.util.Iterator;
  +import java.util.List;
  +
  +import junit.framework.Assert;
   
   import javax.servlet.jsp.JspException;
   import javax.servlet.jsp.PageContext;
  @@ -66,6 +71,9 @@
   import javax.servlet.jsp.tagext.Tag;
   import javax.servlet.jsp.tagext.TryCatchFinally;
   
  +import org.apache.commons.logging.Log;
  +import org.apache.commons.logging.LogFactory;
  +
   /**
    * Convenience class that supports the testing of JSP tag by managing the tag's
    * lifecycle as required by the JSP specification.
  @@ -119,66 +127,51 @@
    *   </ol>
    * </p>
    * 
  - * <h4>Testing Iteration and Body Tags with Lifecycle Interceptors</h4>
  + * <h4>Adding Special Assertions to the Lifecycle</h4>
    * <p>
  - *   In the example above, the tag's lifecycle is simply run through from start
  - *   to finish. However, <code>JspTagLifecycle</code> also let's you get
  - *   <em>inside</em> significant phases of the tag's lifecycle. For this you
  - *   need to use the method
  - *   {@link #invoke(JspTagLifecycle.Interceptor) invoke(Interceptor)}
  - *   supplying a custom
  - *   {@link JspTagLifecycle.Interceptor Interceptor} implementation.
  + *   <code>JspTagLifecycle</code> features a couple of methods that let you 
  + *   easily add JUnit assertions about the tag's lifecycle to the test. For
  + *   example, the method {@link #assertBodySkipped assertBodySkipped()} can be 
  + *   used to assert that tag's body is not evaluated under the conditions set up
  + *   by the test:
  + *   <pre>
  +  IfTag tag = new IfTag();
  +  JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
  +  tag.setTest("false");
  +  lifecycle.assertBodySkipped();
  +  lifecycle.invoke();</pre>
    * </p>
  - * 
    * <p>
  - *   This feature can be used to test iteration and body tags. The following 
  - *   code snippet is a simple example for testing the
  - *   <code>&lt;c:forEach&gt;</code>-tag of the JSTL reference implementation:
  - *   <blockquote><pre>
  + *   An example of a more sophisticated assertion is the
  + *   {@link #assertScopedVariableExposed(String, Object[])}
  + *   method, which can verify that a specific scoped variable gets exposed in
  + *   the body of the tag, and that the exposed variable has a specific value in
  + *   each iteration step:
  + *   <pre>
     ForEachTag tag = new ForEachTag();
     JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
     tag.setVar("item");
  -  tag.setItems("one,two,three");
  -  lifecycle.invoke(new JspTagLifecycle.Interceptor() {
  -    public void evalBody(int iteration, BodyContent body) {
  -      String item = (String)pageContext.findAttribute("item");
  -      if (iteration == 0) {
  -        assertEquals("one", item);
  -      } else if (iteration == 1) {
  -        assertEquals("two", item);
  -      } else if (iteration == 2) {
  -        assertEquals("three", item);
  -      } else {
  -        fail("More iterations than expected!");
  -      }
  -    }
  -  });</pre>
  - * </blockquote></p>
  +  tag.setItems("One,Two,Three");
  +  lifecycle.assertBodyEvaluated();
  +  lifecycle.assertScopedVariableExposed(
  +      "item", new Object[] {"One", "Two", "Three"});
  +  lifecycle.invoke();</pre>
  + * </p>
    * 
  + * <h4>Specifying Nested Content</h4>
    * <p>
  - *   To test a tag that does buffered evaluation of its body content, the 
  - *   {@link JspTagLifecycle.Interceptor#evalBody Interceptor.evalBody()} method
  - *   can be overridden to write the content that the tag will see. The following
  - *   example demonstrates this using the <code>&lt;c:out&gt;</code> tag:
  - *   <blockquote><pre>
  -  OutTag tag = new OutTag();
  -  JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
  -  tag.setValue(null);
  -  lifecycle.invoke(new JspTagLifecycle.Interceptor() {
  -    public void evalBody(int iteration, BodyContent body)
  -        throws IOException {
  -        body.print("Default Value");
  -    }
  -  });
  -  </blockquote>
  + *   <code>JspTagLifecycle</code> let's you add nested tempate text as well as 
  + *   nested tags to the tag under test. The most important use of this feature 
  + *   is testing of collaboration between tags.
    * </p>
    * 
    * @author <a href="mailto:[EMAIL PROTECTED]";>Christopher Lenz</a>
  + * @since Cactus 1.5
    * 
    * @version $Id$
    * @see org.apache.cactus.JspTestCase
    */
  -public class JspTagLifecycle
  +public final class JspTagLifecycle
   {  
       // Inner Classes -----------------------------------------------------------
       
  @@ -188,6 +181,7 @@
        * it is being executed.
        * 
        * @author <a href="mailto:[EMAIL PROTECTED]";>Christopher Lenz</a>
  +     * @since Cactus 1.5
        */
       public abstract static class Interceptor
       {
  @@ -195,15 +189,29 @@
           /**
            * Method called when the body of the tag would be evaluated. Can be
            * used in specific test cases to perform assertions.
  -         *  
  +         * 
  +         * Please note that if you're testing a <code>BodyTag</code>, you
  +         * should not write content to the
  +         * {@link org.apache.cactus.JspTestCase#out} instance variable while 
  +         * the body is being evaluated. This is because the actual implicit
  +         * object <code>out</code> in JSP pages gets replaced by the current 
  +         * nested <code>BodyContent</code>, whereas in <code>JspTestCase</code>
  +         * the <code>out</code> variable always refers to the top level
  +         * <code>JspWriter</code>. Instead, simply use the 
  +         * <code>BodyContent</code> parameter passed into the
  +         * {@link JspTagLifecycle.Interceptor#evalBody evalBody()} method or 
  +         * the <code>JspWriter</code> retrieved by a call to 
  +         * {javax.servlet.jsp.PageContext#getOut pageContext.getOut()}. 
  +         * 
            * @param theIteration The number of times the body has been evaluated
            * @param theBody The body content, or <tt>null</tt> if the tag isn't a
            *        <tt>BodyTag</tt>
  +         * @throws JspException If thrown by a nested tag
            * @throws IOException If an error occurs when reading or writing the
            *         body content
            */
           public void evalBody(int theIteration, BodyContent theBody)
  -            throws IOException
  +            throws JspException, IOException
           {
               // default implementation does nothing
           }
  @@ -220,17 +228,160 @@
       }
       
       /**
  -     * Used internally to avoid having to check for <tt>null</tt>.
  +     * A specialized interceptor that asserts that the tag's body is evaluated
  +     * at least once.
  +     */
  +    private static class AssertBodyEvaluatedInterceptor
  +        extends Interceptor
  +    {
  +        /**
  +         * The actual number of times the tag's body has been evaluated.
  +         */
  +        private int actualNumIterations;
  +        
  +        /**
  +         * The number of times the tag's body is expected to be evaluated.
  +         */
  +        private int expectedNumIterations;
  +        
  +        public AssertBodyEvaluatedInterceptor(int theNumIterations)
  +        {
  +            this.expectedNumIterations = theNumIterations;
  +        }
  +        
  +        public void evalBody(int theIteration, BodyContent theBody)
  +        {
  +            actualNumIterations++;
  +            if (actualNumIterations > expectedNumIterations)
  +            {
  +                Assert.fail("Expected " + expectedNumIterations
  +                    + " iterations, but was " + actualNumIterations);
  +            }
  +        }
  +        
  +        public void skipBody()
  +        {
  +            if (actualNumIterations < expectedNumIterations)
  +            {
  +                Assert.fail("Expected " + expectedNumIterations
  +                    + " iterations, but was " + actualNumIterations);
  +            }
  +        }
  +    }
  +    
  +    /**
  +     * A specialized interceptor that asserts that the tag's body is skipped.
  +     */
  +    private static class AssertBodySkippedInterceptor
  +        extends Interceptor
  +    {
  +        public void evalBody(int theIteration, BodyContent theBody)
  +        {
  +            Assert.fail("Tag body should have been skipped");
  +        }
  +    }
  +    
  +    /**
  +     * A specialized interceptor ...
  +     */
  +    private class AssertScopedVariableExposedInterceptor
  +        extends Interceptor
  +    {
  +        /**
  +         * The name of the scoped variable.
  +         */
  +        private String name;
  +        
  +        /**
  +         * The list of expected values of the variable.
  +         */
  +        private Object[] expectedValues;
  +        
  +        /**
  +         * The scope in which the variable is stored.
  +         */
  +        private int scope;
  +        
  +        public AssertScopedVariableExposedInterceptor(String theName,
  +            Object[] theExpectedValues, int theScope)
  +        {
  +            this.name = theName;
  +            this.expectedValues = theExpectedValues;
  +            this.scope = theScope;
  +        }
  +        
  +        public void evalBody(int theIteration, BodyContent theBody)
  +        {
  +            Assert.assertEquals(expectedValues[theIteration],
  +                pageContext.getAttribute(name, scope));
  +        }
  +    }
  +    
  +    /**
  +     * A specialized interceptor that invokes the lifecycle of a nested tag.
  +     */
  +    private class NestedTagInterceptor
  +        extends Interceptor
  +    {
  +        /**
  +         * The lifecycle object of the nested tag.
  +         */
  +        private JspTagLifecycle lifecycle;
  +        
  +        public NestedTagInterceptor(JspTagLifecycle theLifecycle)
  +        {
  +            this.lifecycle = theLifecycle;
  +        }
  +        
  +        public void evalBody(int theIteration, BodyContent theBody)
  +            throws JspException, IOException
  +        {
  +            lifecycle.invoke();
  +        }
  +    }
  +    
  +    /**
  +     * A specialized interceptor that prints nested template text when the tag's
  +     * body is evaluated.
        */
  -    private static final Interceptor NOOP_INTERCEPTOR = 
  -        new Interceptor()
  +    private class NestedTextInterceptor
  +        extends Interceptor
  +    {
  +        /**
  +         * The nested text.
  +         */
  +        private String text;
  +        
  +        public NestedTextInterceptor(String theText)
           {
  -        };
  +            this.text = theText;
  +        }
  +        
  +        public void evalBody(int theIteration, BodyContent theBody)
  +            throws IOException
  +        {
  +            if (theBody != null)
  +            {
  +                theBody.print(text);
  +            }
  +            else
  +            {
  +                pageContext.getOut().print(text);
  +            }
  +        }
  +    }
  +    
  +    // Class Variables ---------------------------------------------------------
  +    
  +    /**
  +     * The log target.
  +     */
  +    private static Log log = LogFactory.getLog(JspTagLifecycle.class);
       
       // Instance Variables ------------------------------------------------------
       
       /**
  -     * The JSP tag handler.
  +     * The JSP page context.
        */
       private PageContext pageContext;
       
  @@ -240,10 +391,15 @@
       private Tag tag;
       
       /**
  -     * The JSP tag handler.
  +     * The enclosing tag.
        */
       private Tag parent;
       
  +    /**
  +     * The interceptor chain.
  +     */
  +    private List interceptors;
  +    
       // Constructors ------------------------------------------------------------
       
       /**
  @@ -254,40 +410,147 @@
        */
       public JspTagLifecycle(PageContext thePageContext, Tag theTag)
       {
  -        this(thePageContext, theTag, null);
  +        if ((thePageContext == null) || (theTag == null))
  +        {
  +            throw new NullPointerException();
  +        }
  +        this.tag = theTag;
  +        this.pageContext = thePageContext;
  +        tag.setPageContext(pageContext);
  +    }
  +    
  +    // Public Methods ----------------------------------------------------------
  +    
  +    /**
  +     * Adds an interceptor to the interceptor chain.
  +     * 
  +     * @param theInterceptor The interceptor to add
  +     */
  +    public void addInterceptor(Interceptor theInterceptor)
  +    {
  +        if (theInterceptor == null)
  +        {
  +            throw new NullPointerException();
  +        }
  +        if (interceptors == null)
  +        {
  +            interceptors = new ArrayList();
  +        }
  +        interceptors.add(theInterceptor);
       }
       
       /**
  -     * Constructor.
  +     * Adds a nested tag. The tag will be invoked when the body content of the
  +     * enclosing tag is evaluated.
        * 
  -     * @param thePageContext The JSP page context
  -     * @param theTag The JSP tag
  -     * @param theParent The parent tag, or <tt>null</tt>
  +     * @return The lifecycle wrapper for the nested tag, can be used to add 
  +     *         assertions to the nested tag
  +     * @param theNestedTag The tag to be nested
        */
  -    public JspTagLifecycle(PageContext thePageContext, Tag theTag, 
  -        Tag theParent)
  +    public JspTagLifecycle addNestedTag(Tag theNestedTag)
       {
  -        this.tag = theTag;
  -        this.pageContext = thePageContext;
  -        tag.setPageContext(pageContext);
  -        this.parent = theParent;
  -        tag.setParent(parent);
  +        if (theNestedTag == null)
  +        {
  +            throw new NullPointerException();
  +        }
  +        JspTagLifecycle lifecycle =
  +            new JspTagLifecycle(pageContext, theNestedTag);
  +        theNestedTag.setParent(tag);
  +        addInterceptor(new NestedTagInterceptor(lifecycle));
  +        return lifecycle;
       }
       
  -    // Public Methods ----------------------------------------------------------
  +    /**
  +     * Adds template text to nest inside the tag. The text will be printed to 
  +     * the body content when it is evaluated.
  +     * 
  +     * @param theNestedText The string containing the template text
  +     */
  +    public void addNestedText(String theNestedText)
  +    {
  +        if (theNestedText == null)
  +        {
  +            throw new NullPointerException();
  +        }
  +        addInterceptor(new NestedTextInterceptor(theNestedText));
  +    }
  +    
  +    /**
  +     * Adds the assertion that the tag body must be evaluated once in the course
  +     * of the tags lifecycle.
  +     */
  +    public void assertBodyEvaluated()
  +    {
  +        addInterceptor(new AssertBodyEvaluatedInterceptor(1));
  +    }
  +    
  +    /**
  +     * Adds the assertion that the tag body must be evaluated a specific number
  +     * of times in the course of the tags lifecycle.
  +     */
  +    public void assertBodyEvaluated(int theNumIterations)
  +    {
  +        addInterceptor(new AssertBodyEvaluatedInterceptor(theNumIterations));
  +    }
  +    
  +    /**
  +     * Adds the assertion that the tag body must be skipped. Essentially, this
  +     * assertion verifies that the tag returns <code>SKIP_BODY</code> from
  +     * <code>doStartTag()</code>.
  +     */
  +    public void assertBodySkipped()
  +    {
  +        addInterceptor(new AssertBodySkippedInterceptor());
  +    }
       
       /**
  -     * Invokes the tag. The tag should have been populated with its properties
  -     * before calling this method. The tag is not released after the tag's
  -     * lifecycle is over.
  +     * Adds a special assertion that verifies that a specific scoped variable
  +     * is exposed in the body of the tag.
        * 
  -     * @throws JspException If the tag throws an exception
  -     * @throws IOException If an error occurs when reading or writing the body
  -     *         content
  +     * @param theName The name of the variable
  +     * @param theExpectedValues An ordered list containing the expected values 
  +     *                          values of the scoped variable, one for each 
  +     *                          expected iteration step
        */
  -    public void invoke() throws JspException, IOException
  +    public void assertScopedVariableExposed(String theName,
  +                                            Object[] theExpectedValues)
       {
  -        invoke(NOOP_INTERCEPTOR);
  +        assertScopedVariableExposed(theName, theExpectedValues,
  +            PageContext.PAGE_SCOPE);
  +    }
  +    
  +    /**
  +     * Adds a special assertion that verifies that a specific scoped variable
  +     * is exposed in the body of the tag.
  +     * 
  +     * @param theName The name of the variable
  +     * @param theExpectedValues An ordered list containing the expected values 
  +     *                          values of the scoped variable, one for each 
  +     *                          expected iteration step
  +     * @param theScope The scope under which the variable is stored
  +     */
  +    public void assertScopedVariableExposed(String theName,
  +                                            Object[] theExpectedValues,
  +                                            int theScope)
  +    {
  +        if ((theName == null) || (theExpectedValues == null))
  +        {
  +            throw new NullPointerException();
  +        }
  +        if (theExpectedValues.length == 0)
  +        {
  +            throw new IllegalArgumentException();
  +        }
  +        if ((theScope != PageContext.PAGE_SCOPE)
  +         && (theScope != PageContext.REQUEST_SCOPE)
  +         && (theScope != PageContext.SESSION_SCOPE)
  +         && (theScope != PageContext.APPLICATION_SCOPE))
  +        {
  +            throw new IllegalArgumentException();
  +        }
  +        addInterceptor(
  +            new AssertScopedVariableExposedInterceptor(theName,
  +                theExpectedValues, theScope));
       }
       
       /**
  @@ -295,26 +558,19 @@
        * populated with its properties before calling this method. The tag is not
        * released after the tag's lifecycle is over.
        * 
  -     * @param theInterceptor The interceptor that will be notified about 
  -     *        important lifecycle events
        * @throws JspException If the tag throws an exception
        * @throws IOException If an error occurs when reading or writing the body
        *         content
        */
  -    public void invoke(Interceptor theInterceptor) 
  +    public void invoke()
           throws JspException, IOException
       {
  -        if (theInterceptor == null)
  -        {
  -            throw new NullPointerException();
  -        }
  -        BodyContent body = null;
           if (tag instanceof TryCatchFinally)
           {
               TryCatchFinally tryCatchFinally = (TryCatchFinally) tag;
               try
               {
  -                body = invokeTag(theInterceptor);
  +                invokeInternal();
               }
               catch (Throwable t1)
               {
  @@ -329,77 +585,126 @@
               }
               finally
               {
  -                if (body != null)
  -                {
  -                    pageContext.popBody();
  -                    body = null;
  -                }
                   tryCatchFinally.doFinally();
               }
           }
           else
           {
  -            try
  +            invokeInternal();
  +        }
  +    }
  +    
  +    // Private Methods ---------------------------------------------------------
  +    
  +    /**
  +     * Notify all interceptors about a body evaluation.
  +     * 
  +     * @param theIteration The iteration
  +     * @param theBody The body content
  +     * @throws JspException If thrown by a nested tag
  +     * @throws IOException If an error occurs when reading or writing the body
  +     *         content
  +     */
  +    private void fireEvalBody(int theIteration, BodyContent theBody)
  +        throws JspException, IOException
  +    {
  +        if (interceptors != null)
  +        {
  +            for (Iterator i = interceptors.iterator(); i.hasNext();)
               {
  -                body = invokeTag(theInterceptor);
  +                ((Interceptor)i.next()).evalBody(theIteration, theBody);
               }
  -            finally
  +        }
  +    }
  +    
  +    /**
  +     * Notify all interceptors that the body has been skipped.
  +     */
  +    private void fireSkipBody()
  +    {
  +        if (interceptors != null)
  +        {
  +            for (Iterator i = interceptors.iterator(); i.hasNext();)
               {
  -                if (body != null)
  -                {
  -                    pageContext.popBody();
  -                    body = null;
  -                }
  +                ((Interceptor)i.next()).skipBody();
               }
           }
       }
       
  -    // Private Methods ---------------------------------------------------------
  -    
       /**
        * Internal method to invoke a tag without doing exception handling.
        * 
  -     * @param theInterceptor The interceptor that will be notified about 
  -     *        lifecycle events
        * @throws JspException If the tag throws an exception
        * @throws IOException If an error occurs when reading or writing the body
        *         content
  -     * @return The body content, or <tt>null</tt> if the tag didn't request
  -     *         buffered body evaluation
        */
  -    private BodyContent invokeTag(Interceptor theInterceptor)
  +    private void invokeInternal()
           throws JspException, IOException
       {
  -        BodyContent body = null;
           int status = tag.doStartTag();
           if (tag instanceof IterationTag)
           {
               if (status != Tag.SKIP_BODY)
               {
  -                IterationTag iterationTag = (IterationTag) tag;
  -                if ((status == BodyTag.EVAL_BODY_BUFFERED)
  -                    && (tag instanceof BodyTag))
  +                BodyContent body = null;
  +                try
                   {
  -                    BodyTag bodyTag = (BodyTag) tag;
  -                    body = pageContext.pushBody();
  -                    bodyTag.setBodyContent(body);
  -                    bodyTag.doInitBody();
  +                    IterationTag iterationTag = (IterationTag) tag;
  +                    if ((status == BodyTag.EVAL_BODY_BUFFERED)
  +                        && (tag instanceof BodyTag))
  +                    {
  +                        BodyTag bodyTag = (BodyTag) tag;
  +                        if (log.isDebugEnabled())
  +                        {
  +                            log.debug("Pushing body content '"
  +                                + body.getString() + "'");
  +                        }
  +                        body = pageContext.pushBody();
  +                        bodyTag.setBodyContent(body);
  +                        bodyTag.doInitBody();
  +                    }
  +                    int iteration = 0;
  +                    do
  +                    {
  +                        fireEvalBody(iteration, body);
  +                        if (log.isDebugEnabled())
  +                        {
  +                            log.debug("Body evaluated for the "
  +                                + iteration + " time");
  +                        }
  +                        status = iterationTag.doAfterBody();
  +                        iteration++;
  +                    } while (status == IterationTag.EVAL_BODY_AGAIN);
  +                    if (log.isDebugEnabled())
  +                    {
  +                        log.debug("Body skipped");
  +                    }
  +                    fireSkipBody();
                   }
  -                int iteration = 0;
  -                do
  +                finally
                   {
  -                    theInterceptor.evalBody(iteration, body);
  -                    status = iterationTag.doAfterBody();
  -                    iteration++;
  -                } while (status == IterationTag.EVAL_BODY_AGAIN);
  +                    if (body != null)
  +                    {
  +                        if (log.isDebugEnabled())
  +                        {
  +                            log.debug("Popping body content '"
  +                                + body.getString() + "'");
  +                        }
  +                        pageContext.popBody();
  +                        body = null;
  +                    }
  +                }
               }
               else
               {
  -                theInterceptor.skipBody();
  +                if (log.isDebugEnabled())
  +                {
  +                    log.debug("Body skipped");
  +                }
  +                fireSkipBody();
               }
           }
           status = tag.doEndTag();
  -        return body;
       }
       
   }
  
  
  
  1.3       +417 -66   
jakarta-cactus/sample-servlet/src/unit/j2ee13/org/apache/cactus/unit/TestJspTagLifecycle.java
  
  Index: TestJspTagLifecycle.java
  ===================================================================
  RCS file: 
/home/cvs/jakarta-cactus/sample-servlet/src/unit/j2ee13/org/apache/cactus/unit/TestJspTagLifecycle.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- TestJspTagLifecycle.java  26 Nov 2002 09:22:47 -0000      1.2
  +++ TestJspTagLifecycle.java  28 Nov 2002 10:57:40 -0000      1.3
  @@ -54,9 +54,13 @@
   package org.apache.cactus.unit;
   
   import java.io.IOException;
  +
   import javax.servlet.jsp.JspException;
   import javax.servlet.jsp.JspTagException;
  +import javax.servlet.jsp.PageContext;
   import javax.servlet.jsp.tagext.BodyContent;
  +import javax.servlet.jsp.jstl.core.LoopTagStatus;
  +
   import junit.framework.Test;
   import junit.framework.TestSuite;
   
  @@ -64,6 +68,7 @@
   import org.apache.cactus.JspTestCase;
   import org.apache.cactus.WebResponse;
   import org.apache.taglibs.standard.tag.common.core.ChooseTag;
  +import org.apache.taglibs.standard.tag.common.core.OtherwiseTag;
   import org.apache.taglibs.standard.tag.el.core.ForEachTag;
   import org.apache.taglibs.standard.tag.el.core.IfTag;
   import org.apache.taglibs.standard.tag.el.core.OutTag;
  @@ -74,9 +79,9 @@
    * Tests for the <code>JspTagLifecycle</code> extension.
    * 
    * <p>
  - *   The lifecycle helper is tested here by testing the reference implementation
  - *   of the JSP standard tag library (JSTL), available at
  - *   <a href="http://jakarta.apache.org/taglibs/";>.
  + *   The lifecycle helper is tested here by testing the core tags of the 
  + *   reference implementation of the JSP standard tag library (JSTL), available
  + *   at <a href="http://jakarta.apache.org/taglibs/";>.
    * </p>
    * 
    * @author <a href="mailto:[EMAIL PROTECTED]";>Christopher Lenz</a>
  @@ -108,10 +113,185 @@
           // All methods starting with "test" will be executed in the test suite.
           return new TestSuite(TestJspTagLifecycle.class);
       }
  -
  +    
       // Test Methods ------------------------------------------------------------
       
       /**
  +     * Tests whether the constructor throws a <code>NullPointerException</code>
  +     * when passed a <code>null</code> <code>PageContext</code> reference.
  +     */
  +    public void testConstructorWithNullPageContext()
  +    {
  +        try
  +        {
  +            JspTagLifecycle lifecycle = new JspTagLifecycle(null, new OutTag());
  +            fail("Expected NullPointerException");
  +        }
  +        catch (NullPointerException npe)
  +        {
  +            // expected
  +        }
  +    }
  +    
  +    /**
  +     * Tests whether the constructor throws a <code>NullPointerException</code>
  +     * when passed a <code>null</code> <code>Tag</code> reference.
  +     */
  +    public void testConstructorWithNullTag()
  +    {
  +        try
  +        {
  +            JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, null);
  +            fail("Expected NullPointerException");
  +        }
  +        catch (NullPointerException npe)
  +        {
  +            // expected
  +        }
  +    }
  +    
  +    /**
  +     * Tests whether the <code>addInterceptor()</code> method throws a 
  +     * <code>NullPointerException</code> when passed a <code>null</code>
  +     * <code>Interceptor</code> reference.
  +     */
  +    public void testAddInterceptorWithNull()
  +    {
  +        try
  +        {
  +            JspTagLifecycle lifecycle =
  +                new JspTagLifecycle(pageContext, new OutTag());
  +            lifecycle.addInterceptor(null);
  +            fail("Expected NullPointerException");
  +        }
  +        catch (NullPointerException npe)
  +        {
  +            // expected
  +        }
  +    }
  +    
  +    /**
  +     * Tests whether the <code>addNestedTag()</code> method throws a 
  +     * <code>NullPointerException</code> when passed a <code>null</code>
  +     * <code>Interceptor</code> reference.
  +     */
  +    public void testAddNestedTagWithNull()
  +    {
  +        try
  +        {
  +            JspTagLifecycle lifecycle =
  +                new JspTagLifecycle(pageContext, new OutTag());
  +            lifecycle.addNestedTag(null);
  +            fail("Expected NullPointerException");
  +        }
  +        catch (NullPointerException npe)
  +        {
  +            // expected
  +        }
  +    }
  +    
  +    /**
  +     * Tests whether the <code>addNestedText()</code> method throws a 
  +     * <code>NullPointerException</code> when passed a <code>null</code>
  +     * <code>Interceptor</code> reference.
  +     */
  +    public void testAddNestedTextWithNull()
  +    {
  +        try
  +        {
  +            JspTagLifecycle lifecycle =
  +                new JspTagLifecycle(pageContext, new OutTag());
  +            lifecycle.addNestedText(null);
  +            fail("Expected NullPointerException");
  +        }
  +        catch (NullPointerException npe)
  +        {
  +            // expected
  +        }
  +    }
  +    
  +    /**
  +     * Tests whether the <code>assertScopedVariableExposed()</code> method
  +     * throws a <code>NullPointerException</code> when passed a
  +     * <code>null</code> name.
  +     */
  +    public void testAssertScopedVariableExposedWithNullName()
  +    {
  +        try
  +        {
  +            JspTagLifecycle lifecycle =
  +                new JspTagLifecycle(pageContext, new OutTag());
  +            lifecycle.assertScopedVariableExposed(null, new Object[] {"value"});
  +            fail("Expected NullPointerException");
  +        }
  +        catch (NullPointerException npe)
  +        {
  +            // expected
  +        }
  +    }
  +    
  +    /**
  +     * Tests whether the <code>assertScopedVariableExposed()</code> method
  +     * throws a <code>NullPointerException</code> when passed a
  +     * <code>null</code> reference as expected values array.
  +     */
  +    public void testAssertScopedVariableExposedWithNullExpectedValues()
  +    {
  +        try
  +        {
  +            JspTagLifecycle lifecycle =
  +                new JspTagLifecycle(pageContext, new OutTag());
  +            lifecycle.assertScopedVariableExposed("name", null);
  +            fail("Expected NullPointerException");
  +        }
  +        catch (NullPointerException npe)
  +        {
  +            // expected
  +        }
  +    }
  +    
  +    /**
  +     * Tests whether the <code>assertScopedVariableExposed()</code> method
  +     * throws a <code>IllegalArgumentException</code> when passed an empty
  +     * expected values array.
  +     */
  +    public void testAssertScopedVariableExposedWithEmptyExpectedValues()
  +    {
  +        try
  +        {
  +            JspTagLifecycle lifecycle =
  +                new JspTagLifecycle(pageContext, new OutTag());
  +            lifecycle.assertScopedVariableExposed("name", new Object[0]);
  +            fail("Expected IllegalArgumentException");
  +        }
  +        catch (IllegalArgumentException iae)
  +        {
  +            // expected
  +        }
  +    }
  +    
  +    /**
  +     * Tests whether the <code>assertScopedVariableExposed()</code> method
  +     * throws a <code>IllegalArgumentException</code> when passed an invalid
  +     * scope identifier.
  +     */
  +    public void testAssertScopedVariableExposedWithIllegalScope()
  +    {
  +        try
  +        {
  +            JspTagLifecycle lifecycle =
  +                new JspTagLifecycle(pageContext, new OutTag());
  +            lifecycle.assertScopedVariableExposed(
  +                "name", new Object[]{"value"}, 0);
  +            fail("Expected IllegalArgumentException");
  +        }
  +        catch (IllegalArgumentException iae)
  +        {
  +            // expected
  +        }
  +    }
  +    
  +    /**
        * Tests the <code>&lt;c:out&gt;</code>-tag with a proper, literal value for
        * it's <code>value</code> attribute.
        * 
  @@ -124,6 +304,7 @@
           OutTag tag = new OutTag();
           JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
           tag.setValue("Value");
  +        lifecycle.assertBodySkipped();
           lifecycle.invoke();
       }
       
  @@ -140,6 +321,67 @@
       }
       
       /**
  +     * Tests the <code>&lt;c:out&gt;</code>-tag with a proper, literal value for
  +     * it's <code>value</code> attribute that contains special XML characters
  +     * that need to be escaped.
  +     * 
  +     * @throws JspException If the tag throws a JSPException
  +     * @throws IOException If the tag throws an IOException
  +     */
  +    public void testOutTagEscapeXml()
  +        throws JspException, IOException
  +    {
  +        OutTag tag = new OutTag();
  +        JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
  +        tag.setValue("<value/>");
  +        lifecycle.assertBodySkipped();
  +        lifecycle.invoke();
  +    }
  +    
  +    /**
  +     * Verifies that the response has been correctly rendered by the 
  +     * <code>&lt;c:out&gt;</code>-tag.
  +     * 
  +     * @param theResponse The HTTP response
  +     */
  +    public void endOutTagEscapeXml(WebResponse theResponse)
  +    {
  +        String output = theResponse.getText();
  +        assertEquals("&lt;value/&gt;", output);
  +    }
  +    
  +    /**
  +     * Tests the <code>&lt;c:out&gt;</code>-tag with a proper, literal value for
  +     * it's <code>value</code> attribute that contains special XML characters
  +     * that need to be escaped.
  +     * 
  +     * @throws JspException If the tag throws a JSPException
  +     * @throws IOException If the tag throws an IOException
  +     */
  +    public void testOutTagNoEscapeXml()
  +        throws JspException, IOException
  +    {
  +        OutTag tag = new OutTag();
  +        tag.setEscapeXml("false");
  +        JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
  +        tag.setValue("<value/>");
  +        lifecycle.assertBodySkipped();
  +        lifecycle.invoke();
  +    }
  +    
  +    /**
  +     * Verifies that the response has been correctly rendered by the 
  +     * <code>&lt;c:out&gt;</code>-tag.
  +     * 
  +     * @param theResponse The HTTP response
  +     */
  +    public void endOutTagNoEscapeXml(WebResponse theResponse)
  +    {
  +        String output = theResponse.getText();
  +        assertEquals("<value/>", output);
  +    }
  +    
  +    /**
        * Tests the <code>&lt;c:out&gt;</code>-tag with <code>null</code> for
        * it's <code>value</code> attribute, and a proper, literal value for it's
        * <code>default</code> attribute.
  @@ -154,6 +396,7 @@
           JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
           tag.setValue(null);
           tag.setDefault("Default");
  +        lifecycle.assertBodySkipped();
           lifecycle.invoke();
       }
       
  @@ -170,6 +413,39 @@
       }
       
       /**
  +     * Tests the <code>&lt;c:out&gt;</code>-tag with a proper, literal value
  +     * for it's <code>value</code> attribute, as well as a proper, literal 
  +     * value for it's <code>default</code> attribute. In this case, the value
  +     * of the <code>default</code> attribute should be ignored, which is 
  +     * asserted.
  +     * 
  +     * @throws JspException If the tag throws a JSPException
  +     * @throws IOException If the tag throws an IOException
  +     */
  +    public void testOutTagDefaultAttributeIgnored()
  +        throws JspException, IOException
  +    {
  +        OutTag tag = new OutTag();
  +        JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
  +        tag.setValue("Value");
  +        tag.setDefault("Default");
  +        lifecycle.assertBodySkipped();
  +        lifecycle.invoke();
  +    }
  +    
  +    /**
  +     * Verifies that the response has been correctly rendered by the 
  +     * <code>&lt;c:out&gt;</code>-tag.
  +     *  
  +     * @param theResponse The HTTP response
  +     */
  +    public void endOutTagWithDefaultAttributeIgnored(WebResponse theResponse)
  +    {
  +        String output = theResponse.getText();
  +        assertEquals("Value", output);
  +    }
  +    
  +    /**
        * Tests the &lt;c:out&gt;-Tag with a value that evaluates to
        * <code>null</code>, and the default value specified in the tag's body.
        * 
  @@ -182,14 +458,9 @@
           OutTag tag = new OutTag();
           JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
           tag.setValue(null);
  -        lifecycle.invoke(new JspTagLifecycle.Interceptor()
  -        {
  -            public void evalBody(int iteration, BodyContent body)
  -                throws IOException
  -            {
  -                body.print("Default");
  -            }
  -        });
  +        lifecycle.addNestedText("Default");
  +        lifecycle.assertBodyEvaluated();
  +        lifecycle.invoke();
       }
       
       /**
  @@ -197,12 +468,43 @@
        * <code>&lt;c:out&gt;</code>-tag.
        * 
        * @param theResponse The HTTP response
  -     * @todo This test currently fails if commented in
        */
       public void endOutTagDefaultBody(WebResponse theResponse)
       {
           String output = theResponse.getText();
  -        //assertEquals("Default", output);
  +        assertEquals("Default", output);
  +    }
  +    
  +    /**
  +     * Tests the <code>&lt;c:out&gt;</code>-tag with a proper, literal value
  +     * for it's <code>value</code> attribute, as well the default value
  +     * specified in the tag's body. In this case, the tag's body content
  +     * should be ignored, which is asserted.
  +     * 
  +     * @throws JspException If the tag throws a JSPException
  +     * @throws IOException If the tag throws an IOException
  +     */
  +    public void testOutTagDefaultBodyIgnored()
  +        throws JspException, IOException
  +    {
  +        OutTag tag = new OutTag();
  +        JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
  +        tag.setValue("Value");
  +        lifecycle.addNestedText("Default");
  +        lifecycle.assertBodySkipped();
  +        lifecycle.invoke();
  +    }
  +    
  +    /**
  +     * Verifies that the response has been correctly rendered by the 
  +     * <code>&lt;c:out&gt;</code>-tag.
  +     * 
  +     * @param theResponse The HTTP response
  +     */
  +    public void endOutTagDefaultBodyIgnored(WebResponse theResponse)
  +    {
  +        String output = theResponse.getText();
  +        assertEquals("Value", output);
       }
       
       /**
  @@ -239,30 +541,60 @@
           JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
           tag.setVar("Item");
           tag.setItems("One,Two,Three");
  -        lifecycle.invoke(new JspTagLifecycle.Interceptor()
  +        lifecycle.assertBodyEvaluated(3);
  +        lifecycle.assertScopedVariableExposed(
  +            "Item", new Object[] {"One", "Two", "Three"});
  +        lifecycle.invoke();
  +    }
  +    
  +    /**
  +     * Tests the tag &lt;c:forEach&gt; by providing a comma-delimited list of 
  +     * string to it's <code>items</code> attributes, and checking the exposed
  +     * scoped variable on every iteration step.
  +     * 
  +     * @throws JspException If the tag throws a JSPException
  +     * @throws IOException If the tag throws an IOException
  +     */
  +    public void testForEachTagStatus()
  +        throws JspException, IOException
  +    {
  +        ForEachTag tag = new ForEachTag();
  +        JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
  +        tag.setVarStatus("status");
  +        tag.setBegin("0");
  +        tag.setEnd("2");
  +        lifecycle.assertBodyEvaluated(3);
  +        lifecycle.addInterceptor(new JspTagLifecycle.Interceptor()
           {
  -            public void evalBody(int iteration, BodyContent body)
  +            public void evalBody(int theIteration, BodyContent theBody)
               {
  -                String item = (String)pageContext.findAttribute("Item");
  -                assertNotNull(item);
  -                if (iteration == 0)
  +                LoopTagStatus status = (LoopTagStatus)
  +                    pageContext.findAttribute("status");
  +                assertNotNull(status);
  +                if (theIteration == 0)
                   {
  -                    assertEquals("One", item);
  +                    assertEquals(0, status.getIndex());
  +                    assertEquals(1, status.getCount());
  +                    assertTrue(status.isFirst());
  +                    assertFalse(status.isLast());
                   }
  -                else if (iteration == 1)
  +                else if (theIteration == 1)
                   {
  -                    assertEquals("Two", item);
  +                    assertEquals(1, status.getIndex());
  +                    assertEquals(2, status.getCount());
  +                    assertFalse(status.isFirst());
  +                    assertFalse(status.isLast());
                   }
  -                else if (iteration == 2)
  +                else if (theIteration == 2)
                   {
  -                    assertEquals("Three", item);
  -                }
  -                else
  -                {
  -                    fail("More iterations than expected!");
  +                    assertEquals(2, status.getIndex());
  +                    assertEquals(3, status.getCount());
  +                    assertFalse(status.isFirst());
  +                    assertTrue(status.isLast());
                   }
               }
           });
  +        lifecycle.invoke();
       }
       
       /**
  @@ -280,13 +612,21 @@
           IfTag tag = new IfTag();
           JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
           tag.setTest("true");
  -        lifecycle.invoke(new JspTagLifecycle.Interceptor()
  -        {
  -            public void skipBody()
  -            {
  -                fail("Body should have been evaluated!");
  -            }
  -        });
  +        lifecycle.addNestedText("Value");
  +        lifecycle.assertBodyEvaluated();
  +        lifecycle.invoke();
  +    }
  +    
  +    /**
  +     * Verifies that the response has been correctly rendered by the 
  +     * <code>&lt;c:if&gt;</code>-tag.
  +     * 
  +     * @param theResponse The HTTP response
  +     */
  +    public void endIfTagTrue(WebResponse theResponse)
  +    {
  +        String output = theResponse.getText();
  +        assertEquals("Value", output);
       }
       
       /**
  @@ -304,13 +644,21 @@
           IfTag tag = new IfTag();
           JspTagLifecycle lifecycle = new JspTagLifecycle(pageContext, tag);
           tag.setTest("false");
  -        lifecycle.invoke(new JspTagLifecycle.Interceptor()
  -        {
  -            public void evalBody(int iteration, BodyContent body)
  -            {
  -                fail("Body should have been skipped!");
  -            }
  -        });
  +        lifecycle.addNestedText("Value");
  +        lifecycle.assertBodySkipped();
  +        lifecycle.invoke();
  +    }
  +    
  +    /**
  +     * Verifies that the response has been correctly rendered by the 
  +     * <code>&lt;c:if&gt;</code>-tag.
  +     * 
  +     * @param theResponse The HTTP response
  +     */
  +    public void endIfTagFalse(WebResponse theResponse)
  +    {
  +        String output = theResponse.getText();
  +        assertEquals("", output);
       }
       
       /**
  @@ -326,17 +674,17 @@
       public void testWhenTag()
           throws JspException, IOException
       {
  -        WhenTag tag = new WhenTag();
  -        JspTagLifecycle lifecycle =
  -            new JspTagLifecycle(pageContext, tag, new ChooseTag());
  -        tag.setTest("true");
  -        lifecycle.invoke(new JspTagLifecycle.Interceptor()
  -        {
  -            public void skipBody()
  -            {
  -                fail("Body should have been evaluated!");
  -            }
  -        });
  +        ChooseTag chooseTag = new ChooseTag();
  +        JspTagLifecycle chooseLifecycle =
  +            new JspTagLifecycle(pageContext, chooseTag);
  +        
  +        WhenTag whenTag = new WhenTag();
  +        JspTagLifecycle whenLifecycle =
  +            chooseLifecycle.addNestedTag(whenTag);
  +        whenTag.setTest("true");
  +        whenLifecycle.assertBodyEvaluated();
  +        
  +        chooseLifecycle.invoke();
       }
       
       /**
  @@ -353,19 +701,22 @@
       public void testWhenTagNoPermission()
           throws JspException, IOException
       {
  -        ChooseTag parent = new ChooseTag();
  -        parent.subtagSucceeded();
  -        WhenTag tag = new WhenTag();
  -        JspTagLifecycle lifecycle =
  -            new JspTagLifecycle(pageContext, tag, parent);
  -        tag.setTest("true");
  -        lifecycle.invoke(new JspTagLifecycle.Interceptor()
  -        {
  -            public void evalBody(int iteration, BodyContent body)
  -            {
  -                fail("Body should have been skipped!");
  -            }
  -        });
  +        ChooseTag chooseTag = new ChooseTag();
  +        JspTagLifecycle chooseLifecycle =
  +            new JspTagLifecycle(pageContext, chooseTag);
  +        
  +        WhenTag whenTag = new WhenTag();
  +        JspTagLifecycle whenLifecycle =
  +            chooseLifecycle.addNestedTag(whenTag);
  +        whenTag.setTest("false");
  +        whenLifecycle.assertBodySkipped();
  +        
  +        OtherwiseTag otherwiseTag = new OtherwiseTag();
  +        JspTagLifecycle otherwiseLifecycle =
  +            chooseLifecycle.addNestedTag(otherwiseTag);
  +        otherwiseLifecycle.assertBodyEvaluated();
  +        
  +        chooseLifecycle.invoke();
       }
       
       /**
  
  
  

--
To unsubscribe, e-mail:   <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>

Reply via email to