org.apache.wicket.util.tester.BaseWicketTester Maven / Gradle / Ivy
Show all versions of org.ops4j.pax.wicket.service Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.wicket.util.tester;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.wicket.Component;
import org.apache.wicket.Component.IVisitor;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.PageParameters;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Session;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.AjaxSelfUpdatingTimerBehavior;
import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.behavior.BehaviorsUtil;
import org.apache.wicket.behavior.IBehavior;
import org.apache.wicket.feedback.FeedbackMessage;
import org.apache.wicket.feedback.FeedbackMessages;
import org.apache.wicket.feedback.IFeedbackMessageFilter;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.CheckGroup;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.IFormSubmittingComponent;
import org.apache.wicket.markup.html.form.IFormVisitorParticipant;
import org.apache.wicket.markup.html.form.RadioGroup;
import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.IPageLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.link.PageLink;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.protocol.http.HttpSessionStore;
import org.apache.wicket.protocol.http.MockHttpServletResponse;
import org.apache.wicket.protocol.http.MockWebApplication;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WebRequestCycle;
import org.apache.wicket.session.ISessionStore;
import org.apache.wicket.util.diff.DiffUtil;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A helper class to ease unit testing of Wicket applications without the need for a servlet
* container. See javadoc of WicketTester
for example usage. This class can be used as
* is, but JUnit users should use derived class WicketTester
.
*
* @see WicketTester
*
* @author Ingram Chen
* @author Juergen Donnerstag
* @author Frank Bille
* @since 1.2.6
*/
public class BaseWicketTester extends MockWebApplication
{
/** log. */
private static final Logger log = LoggerFactory.getLogger(BaseWicketTester.class);
/**
* @author jcompagner
*/
private static final class TestPageSource implements ITestPageSource
{
private final Page page;
private static final long serialVersionUID = 1L;
/**
* Constructor.
*
* @param page
*/
private TestPageSource(Page page)
{
this.page = page;
}
public Page getTestPage()
{
return page;
}
}
/**
* @author frankbille
*/
public static class DummyWebApplication extends WebApplication
{
@Override
public Class extends Page> getHomePage()
{
return DummyHomePage.class;
}
@Override
protected void outputDevelopmentModeWarning()
{
// Do nothing.
}
@Override
protected ISessionStore newSessionStore()
{
// Don't use a filestore, or we spawn lots of threads, which makes
// things slow.
return new HttpSessionStore(this);
}
}
/**
* Creates WicketTester
and automatically create a WebApplication
, but
* the tester will have no home page.
*/
public BaseWicketTester()
{
this(new DummyWebApplication(), null);
}
/**
* Creates WicketTester
and automatically creates a WebApplication
.
*
* @param
*
* @param homePage
* a home page Class
*/
public BaseWicketTester(final Class homePage)
{
this(new WebApplication()
{
/**
* @see org.apache.wicket.Application#getHomePage()
*/
@Override
public Class extends Page> getHomePage()
{
return homePage;
}
@Override
protected void outputDevelopmentModeWarning()
{
// Do nothing.
}
@Override
protected ISessionStore newSessionStore()
{
// Don't use a filestore, or we spawn lots of threads, which
// makes things slow.
return new HttpSessionStore(this);
}
}, null);
}
/**
* Creates a WicketTester
.
*
* @param application
* a WicketTester
WebApplication
object
*/
public BaseWicketTester(final WebApplication application)
{
this(application, null);
}
/**
* Creates a WicketTester
for unit testing.
*
* @param application
* a WicketTester
WebApplication
object
* @param path
* the absolute path on disk to the WebApplication
's contents (e.g. war
* root) - may be null
*
* @see org.apache.wicket.protocol.http.MockWebApplication#MockWebApplication(org.apache.wicket.protocol.http.WebApplication,
* String)
*/
public BaseWicketTester(final WebApplication application, final String path)
{
super(application, path);
}
/**
* Renders a Page
defined in TestPageSource
. This is usually used when
* a page does not have default constructor. For example, a ViewBook
page requires
* a Book
instance:
*
*
* tester.startPage(new TestPageSource()
* {
* public Page getTestPage()
* {
* Book mockBook = new Book("myBookName");
* return new ViewBook(mockBook);
* }
* });
*
*
* @param testPageSource
* a Page
factory that creates a test page instance
* @return the rendered Page
*/
public final Page startPage(final ITestPageSource testPageSource)
{
startPage(DummyHomePage.class);
DummyHomePage page = (DummyHomePage)getLastRenderedPage();
page.setTestPageSource(testPageSource);
executeListener(page.getTestPageLink());
return getLastRenderedPage();
}
/**
* Builds and processes a request suitable for invoking a listener. The Component
* must implement any of the known IListener
interfaces.
*
* @param component
* the listener to invoke
*/
public void executeListener(Component component)
{
WebRequestCycle cycle = setupRequestAndResponse();
getServletRequest().setRequestToComponent(component);
processRequestCycle(cycle);
}
/**
* Builds and processes a request suitable for executing an AbstractAjaxBehavior
.
*
* @param behavior
* an AbstractAjaxBehavior
to execute
*/
public void executeBehavior(final AbstractAjaxBehavior behavior)
{
CharSequence url = behavior.getCallbackUrl(false);
WebRequestCycle cycle = setupRequestAndResponse(true);
getServletRequest().setRequestToRedirectString(url.toString());
processRequestCycle(cycle);
}
/**
* Renders the Page
.
*
* @param page
* a Page
to render
* @return the rendered Page
*/
public final Page startPage(final Page page)
{
return startPage(new TestPageSource(page));
}
/**
* Renders a Page
from its default constructor.
*
* @param
*
* @param pageClass
* a test Page
class with default constructor
* @return the rendered Page
*/
public final Page startPage(Class pageClass)
{
processRequestCycle(pageClass);
return getLastRenderedPage();
}
/**
* Renders a Page
from its default constructor.
*
* @param
*
* @param pageClass
* a test Page
class with default constructor
* @param parameters
* the parameters to use for the class.
* @return the rendered Page
*/
public final Page startPage(Class pageClass, PageParameters parameters)
{
processRequestCycle(pageClass, parameters);
return getLastRenderedPage();
}
/**
* Creates a {@link FormTester} for the Form
at a given path, and fills all child
* {@link org.apache.wicket.markup.html.form.FormComponent}s with blank String
s.
*
* @param path
* path to FormComponent
* @return a FormTester
instance for testing the Form
* @see #newFormTester(String, boolean)
*/
public FormTester newFormTester(String path)
{
return newFormTester(path, true);
}
/**
* Creates a {@link FormTester} for the Form
at a given path.
*
* @param path
* path to FormComponent
* @param fillBlankString
* specifies whether to fill all child FormComponent
s with blank
* String
s
* @return a FormTester
instance for testing the Form
* @see FormTester
*/
public FormTester newFormTester(String path, boolean fillBlankString)
{
return new FormTester(path, (Form>)getComponentFromLastRenderedPage(path), this,
fillBlankString);
}
/**
* Renders a Panel
defined in TestPanelSource
. The usage is similar to
* {@link #startPage(ITestPageSource)}. Please note that testing Panel
must use the
* supplied panelId as a Component
id.
*
*
* tester.startPanel(new TestPanelSource()
* {
* public Panel getTestPanel(String panelId)
* {
* MyData mockMyData = new MyData();
* return new MyPanel(panelId, mockMyData);
* }
* });
*
*
* @param testPanelSource
* a Panel
factory that creates test Panel
instances
* @return a rendered Panel
*/
public final Panel startPanel(final ITestPanelSource testPanelSource)
{
return (Panel)startPage(new ITestPageSource()
{
private static final long serialVersionUID = 1L;
public Page getTestPage()
{
return new DummyPanelPage(testPanelSource);
}
}).get(DummyPanelPage.TEST_PANEL_ID);
}
/**
* Renders a Panel
from a Panel(String id)
constructor.
*
* @param
*
* @param panelClass
* a test Panel
class with Panel(String id)
constructor
* @return a rendered Panel
*/
public final Panel startPanel(final Class panelClass)
{
return (Panel)startPage(new ITestPageSource()
{
private static final long serialVersionUID = 1L;
public Page getTestPage()
{
return new DummyPanelPage(new ITestPanelSource()
{
private static final long serialVersionUID = 1L;
public Panel getTestPanel(String panelId)
{
try
{
Constructor extends Panel> c = panelClass.getConstructor(String.class);
return c.newInstance(panelId);
}
catch (SecurityException e)
{
throw convertoUnexpect(e);
}
catch (NoSuchMethodException e)
{
throw convertoUnexpect(e);
}
catch (InstantiationException e)
{
throw convertoUnexpect(e);
}
catch (IllegalAccessException e)
{
throw convertoUnexpect(e);
}
catch (InvocationTargetException e)
{
throw convertoUnexpect(e);
}
}
});
}
}).get(DummyPanelPage.TEST_PANEL_ID);
}
/**
* A helper method for starting a component for a test without attaching it to a Page.
*
* Components which are somehow dependent on the page structure can not be currently tested with
* this method.
*
* Example:
*
* UserDataView view = new UserDataView("view", new ListDataProvider(userList));
* tester.startComponent(view); assertEquals(4, view.size());
*
* @param component
*/
public void startComponent(Component component)
{
if (component instanceof FormComponent)
{
((FormComponent>)component).processInput();
}
component.beforeRender();
}
/**
* Throw "standard" WicketRuntimeException
*
* @param e
* @return RuntimeException
*/
private RuntimeException convertoUnexpect(Exception e)
{
return new WicketRuntimeException("tester: unexpected", e);
}
/**
* Gets the component with the given path from last rendered page. This method fails in case the
* component couldn't be found, and it will return null if the component was found, but is not
* visible.
*
* @param path
* Path to component
* @return The component at the path
* @see org.apache.wicket.MarkupContainer#get(String)
*/
public Component getComponentFromLastRenderedPage(String path)
{
final Component component = getLastRenderedPage().get(path);
if (component == null)
{
fail("path: '" + path + "' does not exist for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
return component;
}
if (component.isVisibleInHierarchy())
{
return component;
}
return null;
}
/**
* assert the text of Label
component.
*
* @param path
* path to Label
component
* @param expectedLabelText
* expected label text
* @return a Result
*/
public Result hasLabel(String path, String expectedLabelText)
{
Label label = (Label)getComponentFromLastRenderedPage(path);
return isEqual(expectedLabelText, label.getDefaultModelObjectAsString());
}
/**
* assert PageLink
link to page class.
*
* @param
*
* @param path
* path to PageLink
component
* @param expectedPageClass
* expected page class to link
* @return a Result
*/
public Result isPageLink(String path, Class expectedPageClass)
{
PageLink> pageLink = (PageLink>)getComponentFromLastRenderedPage(path);
try
{
for (Class> type = pageLink.getClass(); type != PageLink.class.getSuperclass(); type = type.getSuperclass())
{
try
{
Field iPageLinkField = type.getDeclaredField("pageLink");
iPageLinkField.setAccessible(true);
IPageLink iPageLink = (IPageLink)iPageLinkField.get(pageLink);
return isEqual(expectedPageClass, iPageLink.getPageIdentity());
}
catch (NoSuchFieldException e)
{
continue;
}
}
throw new WicketRuntimeException(
"Is this realy a PageLink? Cannot find 'pageLink' field");
}
catch (SecurityException e)
{
throw convertoUnexpect(e);
}
catch (IllegalAccessException e)
{
throw convertoUnexpect(e);
}
}
/**
* assert component class
*
* @param
*
* @param path
* path to component
* @param expectedComponentClass
* expected component class
* @return a Result
*/
public Result isComponent(String path, Class expectedComponentClass)
{
Component component = getComponentFromLastRenderedPage(path);
if (component == null)
{
return Result.fail("Component not found: " + path);
}
return isTrue("component '" + Classes.simpleName(component.getClass()) + "' is not type:" +
Classes.simpleName(expectedComponentClass),
expectedComponentClass.isAssignableFrom(component.getClass()));
}
/**
* assert component visible.
*
* @param path
* path to component
* @return a Result
*/
public Result isVisible(String path)
{
Component component = getLastRenderedPage().get(path);
if (component == null)
{
fail("path: '" + path + "' does no exist for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
}
return isTrue("component '" + path + "' is not visible", component.isVisibleInHierarchy());
}
/**
* assert component invisible.
*
* @param path
* path to component
* @return a Result
*/
public Result isInvisible(String path)
{
return isNull("component '" + path + "' is visible", getComponentFromLastRenderedPage(path));
}
/**
* assert component enabled.
*
* @param path
* path to component
* @return a Result
*/
public Result isEnabled(String path)
{
Component component = getLastRenderedPage().get(path);
if (component == null)
{
fail("path: '" + path + "' does no exist for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
}
return isTrue("component '" + path + "' is disabled", component.isEnabledInHierarchy());
}
/**
* assert component disabled.
*
* @param path
* path to component
* @return a Result
*/
public Result isDisabled(String path)
{
Component component = getLastRenderedPage().get(path);
if (component == null)
{
fail("path: '" + path + "' does no exist for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
}
return isFalse("component '" + path + "' is enabled", component.isEnabledInHierarchy());
}
/**
* assert component required.
*
* @param path
* path to component
* @return a Result
*/
public Result isRequired(String path)
{
Component component = getLastRenderedPage().get(path);
if (component == null)
{
fail("path: '" + path + "' does no exist for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
}
else if (component instanceof FormComponent == false)
{
fail("path: '" + path + "' is not a form component");
}
return isRequired((FormComponent>)component);
}
/**
* assert component required.
*
* @param component
* a form component
* @return a Result
*/
public Result isRequired(FormComponent> component)
{
return isTrue("component '" + component + "' is not required", component.isRequired());
}
/**
* assert the content of last rendered page contains(matches) regex pattern.
*
* @param pattern
* reqex pattern to match
* @return a Result
*/
public Result ifContains(String pattern)
{
return isTrue("pattern '" + pattern + "' not found", getServletResponse().getDocument()
.matches("(?s).*" + pattern + ".*"));
}
/**
* assert the content of last rendered page contains(matches) regex pattern.
*
* @param pattern
* reqex pattern to match
* @return a Result
*/
public Result ifContainsNot(String pattern)
{
return isFalse("pattern '" + pattern + "' found", getServletResponse().getDocument()
.matches("(?s).*" + pattern + ".*"));
}
/**
* assert the model of {@link ListView} use expectedList
*
* @param path
* path to {@link ListView} component
* @param expectedList
* expected list in the model of {@link ListView}
*/
public void assertListView(String path, List> expectedList)
{
ListView> listView = (ListView>)getComponentFromLastRenderedPage(path);
WicketTesterHelper.assertEquals(expectedList, listView.getList());
}
/**
* Click the {@link Link} in the last rendered Page.
*
* Simulate that AJAX is enabled.
*
* @see WicketTester#clickLink(String, boolean)
* @param path
* Click the Link
in the last rendered Page.
*/
public void clickLink(String path)
{
clickLink(path, true);
}
/**
* Click the {@link Link} in the last rendered Page.
*
* This method also works for {@link AjaxLink}, {@link AjaxFallbackLink} and
* {@link AjaxSubmitLink}.
*
* On AjaxLinks and AjaxFallbackLinks the onClick method is invoked with a valid
* AjaxRequestTarget. In that way you can test the flow of your application when using AJAX.
*
* When clicking an AjaxSubmitLink the form, which the AjaxSubmitLink is attached to is first
* submitted, and then the onSubmit method on AjaxSubmitLink is invoked. If you have changed
* some values in the form during your test, these will also be submitted. This should not be
* used as a replacement for the {@link FormTester} to test your forms. It should be used to
* test that the code in your onSubmit method in AjaxSubmitLink actually works.
*
* This method is also able to simulate that AJAX (javascript) is disabled on the client. This
* is done by setting the isAjax parameter to false. If you have an AjaxFallbackLink you can
* then check that it doesn't fail when invoked as a normal link.
*
* @param path
* path to Link
component
* @param isAjax
* Whether to simulate that AJAX (javascript) is enabled or not. If it's false then
* AjaxLink and AjaxSubmitLink will fail, since it wouldn't work in real life.
* AjaxFallbackLink will be invoked with null as the AjaxRequestTarget parameter.
*/
public void clickLink(String path, boolean isAjax)
{
Component linkComponent = getComponentFromLastRenderedPage(path);
checkUsability(linkComponent);
// if the link is an AjaxLink, we process it differently
// than a normal link
if (linkComponent instanceof AjaxLink)
{
// If it's not ajax we fail
if (isAjax == false)
{
fail("Link " + path + "is an AjaxLink and will " +
"not be invoked when AJAX (javascript) is disabled.");
}
AjaxLink> link = (AjaxLink>)linkComponent;
setupRequestAndResponse(true);
WebRequestCycle requestCycle = createRequestCycle();
AjaxRequestTarget target = getApplication().newAjaxRequestTarget(link.getPage());
requestCycle.setRequestTarget(target);
link.onClick(target);
// process the request target
processRequestCycle(requestCycle);
}
// AjaxFallbackLinks is processed like an AjaxLink if isAjax is true
// If it's not handling of the linkComponent is passed through to the
// Link.
else if (linkComponent instanceof AjaxFallbackLink && isAjax)
{
AjaxFallbackLink> link = (AjaxFallbackLink>)linkComponent;
setupRequestAndResponse(true);
WebRequestCycle requestCycle = createRequestCycle();
AjaxRequestTarget target = getApplication().newAjaxRequestTarget(link.getPage());
requestCycle.setRequestTarget(target);
link.onClick(target);
// process the request target
processRequestCycle(requestCycle);
}
// if the link is an AjaxSubmitLink, we need to find the form
// from it using reflection so we know what to submit.
else if (linkComponent instanceof AjaxSubmitLink)
{
// If it's not ajax we fail
if (isAjax == false)
{
fail("Link " + path + "is an AjaxSubmitLink and " +
"will not be invoked when AJAX (javascript) is disabled.");
}
AjaxSubmitLink link = (AjaxSubmitLink)linkComponent;
// We cycle through the attached behaviors and select the
// LAST matching behavior as the one we handle.
List behaviors = link.getBehaviors();
AjaxFormSubmitBehavior ajaxFormSubmitBehavior = null;
for (IBehavior behavior : behaviors)
{
if (behavior instanceof AjaxFormSubmitBehavior)
{
AjaxFormSubmitBehavior submitBehavior = (AjaxFormSubmitBehavior)behavior;
ajaxFormSubmitBehavior = submitBehavior;
}
}
String failMessage = "No form submit behavior found on the submit link. Strange!!";
notNull(failMessage, ajaxFormSubmitBehavior);
WebRequestCycle requestCycle = setupRequestAndResponse(true);
setupAjaxSubmitRequestParameters(linkComponent, ajaxFormSubmitBehavior);
// Ok, finally we "click" the link
ajaxFormSubmitBehavior.onRequest();
// process the request target
processRequestCycle(requestCycle);
}
/*
* If the link is a submitlink then we pretend to have clicked it
*/
else if (linkComponent instanceof SubmitLink)
{
SubmitLink submitLink = (SubmitLink)linkComponent;
String pageRelativePath = submitLink.getInputName();
getParametersForNextRequest().put(pageRelativePath, new String[] { "x" });
Form> form = submitLink.getForm();
form.visitFormComponents(new FormComponent.IVisitor()
{
public Object formComponent(IFormVisitorParticipant formComponent)
{
FormComponent> component = (FormComponent>)formComponent;
if (getParametersForNextRequest().containsKey(component.getInputName()) == false)
{
getParametersForNextRequest().put(component.getInputName(),
new String[] { component.getDefaultModelObjectAsString() });
}
return IVisitor.CONTINUE_TRAVERSAL;
}
});
submitForm(submitLink.getForm().getPageRelativePath());
}
// if the link is a normal link (or ResourceLink)
else if (linkComponent instanceof AbstractLink)
{
AbstractLink link = (AbstractLink)linkComponent;
/*
* If the link is a bookmarkable link, then we need to transfer the parameters to the
* next request.
*/
if (link instanceof BookmarkablePageLink)
{
BookmarkablePageLink> bookmarkablePageLink = (BookmarkablePageLink>)link;
try
{
BookmarkablePageLink.class.getDeclaredField("parameters");
Method getParametersMethod = BookmarkablePageLink.class.getDeclaredMethod(
"getPageParameters", (Class>[])null);
getParametersMethod.setAccessible(true);
PageParameters parameters = (PageParameters)getParametersMethod.invoke(
bookmarkablePageLink, (Object[])null);
setParametersForNextRequest(parameters.toRequestParameters());
}
catch (Exception e)
{
fail("Internal error in WicketTester. "
+ "Please report this in Wickets Issue Tracker.");
}
}
executeListener(link);
}
else
{
fail("Link " + path + " is not a Link, AjaxLink, AjaxFallbackLink or AjaxSubmitLink");
}
}
/**
* Submits the Form
in the last rendered Page
.
*
* @param path
* path to Form
component
*/
public void submitForm(String path)
{
Form> form = (Form>)getComponentFromLastRenderedPage(path);
executeListener(form);
}
/**
* Sets a parameter for the Component
with the given path to be used with the next
* request.
*
* NOTE: this method only works when a Page
was rendered first.
*
* @param componentPath
* path to the Component
* @param value
* the parameter value to set
*/
public void setParameterForNextRequest(String componentPath, Object value)
{
if (getLastRenderedPage() == null)
{
fail("before using this method, at least one page has to be rendered");
}
Component c = getComponentFromLastRenderedPage(componentPath);
if (c == null)
{
fail("component " + componentPath + " was not found");
return;
}
if (c instanceof FormComponent)
{
getParametersForNextRequest().put(((FormComponent>)c).getInputName(),
new String[] { value.toString() });
}
else
{
getParametersForNextRequest().put(c.getPath(), new String[] { value.toString() });
}
}
/**
* Asserts the last rendered Page
class.
*
* FIXME explain why the code is so complicated to compare two classes, or simplify
*
* @param
*
* @param expectedRenderedPageClass
* expected class of last rendered page
* @return a Result
*/
public Result isRenderedPage(Class expectedRenderedPageClass)
{
Page page = getLastRenderedPage();
if (page == null)
{
return Result.fail("page was null");
}
if (!page.getClass().isAssignableFrom(expectedRenderedPageClass))
{
return isEqual(Classes.simpleName(expectedRenderedPageClass),
Classes.simpleName(page.getClass()));
}
return Result.pass();
}
/**
* Asserts last rendered Page
against an expected HTML document.
*
* Use -Dwicket.replace.expected.results=true
to automatically replace the expected
* output file.
*
*
* @param pageClass
* used to load the File
(relative to clazz
package)
* @param filename
* expected output File
name
* @throws Exception
*/
public void assertResultPage(final Class> pageClass, final String filename) throws Exception
{
// Validate the document
String document = getServletResponse().getDocument();
DiffUtil.validatePage(document, pageClass, filename, true);
}
/**
* Asserts last rendered Page
against an expected HTML document as a
* String
.
*
* @param expectedDocument
* expected output
* @return a Result
* @throws Exception
*/
public Result isResultPage(final String expectedDocument) throws Exception
{
// Validate the document
String document = getServletResponse().getDocument();
return isTrue("expected rendered page equals", document.equals(expectedDocument));
}
/**
* Asserts no error-level feedback messages.
*
* @return a Result
*/
public Result hasNoErrorMessage()
{
List messages = getMessages(FeedbackMessage.ERROR);
return isTrue(
"expect no error message, but contains\n" + WicketTesterHelper.asLined(messages),
messages.isEmpty());
}
/**
* Asserts no info-level feedback messages.
*
* @return a Result
*/
public Result hasNoInfoMessage()
{
List messages = getMessages(FeedbackMessage.INFO);
return isTrue(
"expect no info message, but contains\n" + WicketTesterHelper.asLined(messages),
messages.isEmpty());
}
/**
* Retrieves FeedbackMessages
.
*
* @param level
* level of feedback message, for example:
* FeedbackMessage.DEBUG or FeedbackMessage.INFO.. etc
* @return List
of messages (as String
s)
* @see FeedbackMessage
*/
public List getMessages(final int level)
{
FeedbackMessages feedbackMessages = Session.get().getFeedbackMessages();
List allMessages = feedbackMessages.messages(new IFeedbackMessageFilter()
{
private static final long serialVersionUID = 1L;
public boolean accept(FeedbackMessage message)
{
return message.getLevel() == level;
}
});
List actualMessages = new ArrayList();
for (FeedbackMessage message : allMessages)
{
actualMessages.add(message.getMessage());
}
return actualMessages;
}
/**
* Dumps the source of last rendered Page
.
*/
public void dumpPage()
{
log.info(getServletResponse().getDocument());
}
/**
* Dumps the Component
trees.
*/
public void debugComponentTrees()
{
debugComponentTrees("");
}
/**
* Dumps the Component
trees to log. Show only the Component
s whose
* paths contain the filter String
.
*
* @param filter
* a filter String
*/
public void debugComponentTrees(String filter)
{
log.info("debugging ----------------------------------------------");
for (WicketTesterHelper.ComponentData obj : WicketTesterHelper.getComponentData(getLastRenderedPage()))
{
if (obj.path.matches(".*" + filter + ".*"))
{
log.info("path\t" + obj.path + " \t" + obj.type + " \t[" + obj.value + "]");
}
}
}
/**
* Tests that a Component
has been added to a AjaxRequestTarget
, using
* {@link AjaxRequestTarget#addComponent(Component)}. This method actually tests that a
* Component
is on the Ajax response sent back to the client.
*
* PLEASE NOTE! This method doesn't actually insert the Component
in the client DOM
* tree, using Javascript. But it shouldn't be needed because you have to trust that the Wicket
* Ajax Javascript just works.
*
* @param component
* the Component
to test
* @return a Result
*/
public Result isComponentOnAjaxResponse(Component component)
{
String failMessage = "A component which is null could not have been added to the AJAX response";
notNull(failMessage, component);
Result result;
// test that the component renders the placeholder tag if it's not
// visible
if (!component.isVisible())
{
failMessage = "A component which is invisible and doesn't render a placeholder tag"
+ " will not be rendered at all and thus won't be accessible for subsequent AJAX interaction";
result = isTrue(failMessage, component.getOutputMarkupPlaceholderTag());
if (result.wasFailed())
{
return result;
}
}
// Get the AJAX response
String ajaxResponse = getServletResponse().getDocument();
// Test that the previous response was actually a AJAX response
failMessage = "The Previous response was not an AJAX response. "
+ "You need to execute an AJAX event, using clickLink, before using this assert";
boolean isAjaxResponse = Pattern.compile(
"^<\\?xml version=\"1.0\" encoding=\".*?\"\\?>")
.matcher(ajaxResponse)
.find();
result = isTrue(failMessage, isAjaxResponse);
if (result.wasFailed())
{
return result;
}
// See if the component has a markup id
String markupId = component.getMarkupId();
failMessage = "The component doesn't have a markup id, "
+ "which means that it can't have been added to the AJAX response";
result = isTrue(failMessage, !Strings.isEmpty(markupId));
if (result.wasFailed())
{
return result;
}
// Look for that the component is on the response, using the markup id
boolean isComponentInAjaxResponse = ajaxResponse.matches("(?s).*]*?>.*");
failMessage = "Component wasn't found in the AJAX response";
return isTrue(failMessage, isComponentInAjaxResponse);
}
/**
* Simulates the firing of an Ajax event.
*
* @see #executeAjaxEvent(Component, String)
*
* @since 1.2.3
* @param componentPath
* the Component
path
* @param event
* the event which we simulate being fired. If event
is
* null
, the test will fail.
*/
public void executeAjaxEvent(String componentPath, String event)
{
Component component = getComponentFromLastRenderedPage(componentPath);
executeAjaxEvent(component, event);
}
/**
* Simulates the firing of all ajax timer behaviors on the page
*
* @param page
* the page which timers will be executed
*/
public void executeAllTimerBehaviors(final MarkupContainer page)
{
// execute all timer behaviors for the page itself
internalExecuteAllTimerBehaviors(page);
// and for all its children
page.visitChildren(Component.class, new IVisitor()
{
public Object component(final Component component)
{
internalExecuteAllTimerBehaviors(component);
return CONTINUE_TRAVERSAL;
}
});
}
private void internalExecuteAllTimerBehaviors(final Component component)
{
List behaviors = BehaviorsUtil.getBehaviors(component, AbstractAjaxTimerBehavior.class);
for (IBehavior b : behaviors)
{
AbstractAjaxTimerBehavior timer = (AbstractAjaxTimerBehavior) b;
if (!timer.isStopped())
{
if (log.isDebugEnabled())
{
log.debug("Triggering AjaxSelfUpdatingTimerBehavior: {}", component.getClassRelativePath());
}
checkUsability(component);
executeBehavior(timer);
}
}
}
/**
* Simulates the firing of an Ajax event. You add an Ajax event to a Component
by
* using:
*
*
* ...
* component.add(new AjaxEventBehavior("ondblclick") {
* public void onEvent(AjaxRequestTarget) {}
* });
* ...
*
*
* You can then test that the code inside onEvent
actually does what it's supposed
* to, using the WicketTester
:
*
*
* ...
* tester.executeAjaxEvent(component, "ondblclick");
* // Test that the code inside onEvent is correct.
* ...
*
*
* This also works with AjaxFormSubmitBehavior
, where it will "submit" the
* Form
before executing the command.
*
* PLEASE NOTE! This method doesn't actually insert the Component
in the client DOM
* tree, using Javascript.
*
*
* @param component
* the Component
that has the AjaxEventBehavior
we want to
* test. If the Component
is null
, the test will fail.
* @param event
* the event to simulate being fired. If event
is null
, the
* test will fail.
*/
public void executeAjaxEvent(final Component component, final String event)
{
setCreateAjaxRequest(true);
String failMessage = "Can't execute event on a component which is null.";
notNull(failMessage, component);
failMessage = "event must not be null";
notNull(failMessage, event);
checkUsability(component);
// Run through all the behavior and select the LAST ADDED behavior which
// matches the event parameter.
AjaxEventBehavior ajaxEventBehavior = null;
List behaviors = component.getBehaviors();
for (IBehavior behavior : behaviors)
{
// AjaxEventBehavior is the one to look for
if (behavior instanceof AjaxEventBehavior)
{
AjaxEventBehavior tmp = (AjaxEventBehavior)behavior;
if (event.equals(tmp.getEvent()))
{
ajaxEventBehavior = tmp;
}
}
}
// If there haven't been found any event behaviors on the component
// which matches the parameters we fail.
failMessage = "No AjaxEventBehavior found on component: " + component.getId() +
" which matches the event: " + event;
notNull(failMessage, ajaxEventBehavior);
// when the requestcycle is not created via
// setupRequestAndResponse(true), than create a new
// one
WebRequestCycle requestCycle = resolveRequestCycle();
if (!requestCycle.getWebRequest().isAjax())
{
throw new IllegalStateException(
"The ServletWebRequest was created without wicket-ajax header. Please use tester.setCreateAjaxRequest(true)");
}
// If the event is an FormSubmitBehavior then also "submit" the form
if (ajaxEventBehavior instanceof AjaxFormSubmitBehavior)
{
AjaxFormSubmitBehavior ajaxFormSubmitBehavior = (AjaxFormSubmitBehavior)ajaxEventBehavior;
setupAjaxSubmitRequestParameters(component, ajaxFormSubmitBehavior);
}
// process the event
ajaxEventBehavior.onRequest();
// process the request target
processRequestCycle(requestCycle);
}
/**
*
* @return WebRequestCycle
*/
protected WebRequestCycle resolveRequestCycle()
{
// initialize the request only if needed to allow the user to pass
// request parameters, see WICKET-254
WebRequestCycle requestCycle;
if (RequestCycle.get() == null)
{
requestCycle = setupRequestAndResponse();
}
else
{
requestCycle = (WebRequestCycle)RequestCycle.get();
// If a ajax request is requested but the existing is not, than we
// still need to create
// a new one
if ((requestCycle.getWebRequest().isAjax() == false) && (isCreateAjaxRequest() == true))
{
setParametersForNextRequest(requestCycle.getWebRequest().getParameterMap());
requestCycle = setupRequestAndResponse();
}
}
return requestCycle;
}
/**
* Retrieves a TagTester
based on a wicket:id
. If more
* Component
s exist with the same wicket:id
in the markup, only the
* first one is returned.
*
* @param wicketId
* the wicket:id
to search for
* @return the TagTester
for the tag which has the given wicket:id
*/
public TagTester getTagByWicketId(String wicketId)
{
return TagTester.createTagByAttribute(getServletResponse().getDocument(), "wicket:id",
wicketId);
}
/**
* Modified version of BaseWicketTester#getTagByWicketId(String) that returns all matching tags
* instead of just the first.
*
* @see BaseWicketTester#getTagByWicketId(String)
*/
public static List getTagsByWicketId(WicketTester tester, String wicketId)
{
return TagTester.createTagsByAttribute(tester.getServletResponse().getDocument(),
"wicket:id", wicketId, false);
}
/**
* Retrieves a TagTester
based on an DOM id. If more Component
s exist
* with the same id in the markup, only the first one is returned.
*
* @param id
* the DOM id to search for.
* @return the TagTester
for the tag which has the given DOM id
*/
public TagTester getTagById(String id)
{
return TagTester.createTagByAttribute(getServletResponse().getDocument(), "id", id);
}
/**
* Helper method for all the places where an Ajax call should submit an associated
* Form
.
*
* @param component
* The component the behavior is attached to
* @param behavior
* The AjaxFormSubmitBehavior
with the Form
to "submit"
*/
private void setupAjaxSubmitRequestParameters(final Component component,
AjaxFormSubmitBehavior behavior)
{
// The form that needs to be "submitted".
Form> form = behavior.getForm();
String failMessage = "No form attached to the submitlink.";
notNull(failMessage, form);
checkUsability(form);
final Map requestParameters = getServletRequest().getParameterMap();
/*
* Means that an button or an ajax link was clicked and needs to be added to the request
* parameters to their form component correctly resolves the submit origin
*/
if (component instanceof Button)
{
Button clickedButton = (Button)component;
getServletRequest().setParameter(clickedButton.getInputName(), clickedButton.getValue());
}
else if (component instanceof AjaxSubmitLink)
{
String inputName = ((IFormSubmittingComponent)component).getInputName();
requestParameters.put(inputName, new String[] { "x" });
}
form.visitFormComponents(new FormComponent.AbstractVisitor()
{
@Override
public void onFormComponent(FormComponent> formComponent)
{
/*
* It is important to don't add every button input name as an request parameter to
* respect the submit origin
*/
if (!(formComponent instanceof RadioGroup) &&
!(formComponent instanceof CheckGroup) && !(formComponent instanceof Button) &&
formComponent.isVisibleInHierarchy() && formComponent.isEnabledInHierarchy())
{
if (!((formComponent instanceof IFormSubmittingComponent) && (component instanceof IFormSubmittingComponent)) ||
(component == formComponent))
{
String name = formComponent.getInputName();
String value = formComponent.getValue();
// Set request parameter with the field value, but do not modify an existing
// request parameter explicitly set using FormTester.setValue()
if (!getServletRequest().getParameterMap().containsKey(name) &&
!getParametersForNextRequest().containsKey(name))
{
getServletRequest().setParameter(name, value);
getParametersForNextRequest().put(name, new String[] { value });
}
}
}
}
});
}
/**
* Retrieves the content type from the response header.
*
* @return the content type from the response header
*/
public String getContentTypeFromResponseHeader()
{
String contentType = ((MockHttpServletResponse)getWicketResponse().getHttpServletResponse()).getHeader("Content-Type");
if (contentType == null)
{
throw new WicketRuntimeException("No Content-Type header found");
}
return contentType;
}
/**
* Retrieves the content length from the response header.
*
* @return the content length from the response header
*/
public int getContentLengthFromResponseHeader()
{
String contentLength = ((MockHttpServletResponse)getWicketResponse().getHttpServletResponse()).getHeader("Content-Length");
if (contentLength == null)
{
throw new WicketRuntimeException("No Content-Length header found");
}
return Integer.parseInt(contentLength);
}
/**
* Retrieves the last-modified value from the response header.
*
* @return the last-modified value from the response header
*/
public String getLastModifiedFromResponseHeader()
{
return ((MockHttpServletResponse)getWicketResponse().getHttpServletResponse()).getHeader("Last-Modified");
}
/**
* Retrieves the content disposition from the response header.
*
* @return the content disposition from the response header
*/
public String getContentDispositionFromResponseHeader()
{
return ((MockHttpServletResponse)getWicketResponse().getHttpServletResponse()).getHeader("Content-Disposition");
}
private Result isTrue(String message, boolean condition)
{
if (condition)
{
return Result.pass();
}
return Result.fail(message);
}
private Result isFalse(String message, boolean condition)
{
if (!condition)
{
return Result.pass();
}
return Result.fail(message);
}
protected final Result isEqual(Object expected, Object actual)
{
if (expected == null && actual == null)
{
return Result.pass();
}
if (expected != null && expected.equals(actual))
{
return Result.pass();
}
String message = "expected:<" + expected + "> but was:<" + actual + ">";
return Result.fail(message);
}
private void notNull(String message, Object object)
{
if (object == null)
{
fail(message);
}
}
private Result isNull(String message, Object object)
{
if (object != null)
{
return Result.fail(message);
}
return Result.pass();
}
/**
* Checks whether a component is visible and/or enabled before usage
*
* @param component
*/
protected void checkUsability(final Component component)
{
if (component.isVisibleInHierarchy() == false)
{
fail("The component is currently not visible in the hierarchy and thus you can not be used." +
" Component: " + component);
}
if (component.isEnabledInHierarchy() == false)
{
fail("The component is currently not enabled in the hierarchy and thus you can not be used." +
" Component: " + component);
}
}
protected final void fail(String message)
{
throw new WicketRuntimeException(message);
}
/**
* @param rc
*/
// FIXME 1.5: REMOVE THIS HACK. Currently there is no way to call
// requestcycle.onbeginrequest() from outside and since tester shortcircuits
// the normal
// workflow it is necessary to call onbeginrequest manually
@Deprecated
public static void callOnBeginRequest(RequestCycle rc)
{
try
{
Method method = RequestCycle.class.getDeclaredMethod("onBeginRequest", (Class>[])null);
method.setAccessible(true);
method.invoke(rc, (Object[])null);
}
catch (Exception e)
{
throw new RuntimeException("Exception invoking requestcycle.onbeginrequest()", e);
}
}
}