blob: 828bd33c26cdf1e9705962bc203ef68e3366b7ec [file] [log] [blame]
/*
* 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 static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpSession;
import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IPageManagerProvider;
import org.apache.wicket.IPageRendererProvider;
import org.apache.wicket.IRequestCycleProvider;
import org.apache.wicket.IRequestListener;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.Session;
import org.apache.wicket.ThreadContext;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxEventBehavior;
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.IAjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.core.request.handler.BookmarkableListenerRequestHandler;
import org.apache.wicket.core.request.handler.BookmarkablePageRequestHandler;
import org.apache.wicket.core.request.handler.IPageProvider;
import org.apache.wicket.core.request.handler.ListenerRequestHandler;
import org.apache.wicket.core.request.handler.PageAndComponentProvider;
import org.apache.wicket.core.request.handler.PageProvider;
import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
import org.apache.wicket.feedback.ExactLevelFeedbackMessageFilter;
import org.apache.wicket.feedback.FeedbackCollector;
import org.apache.wicket.feedback.FeedbackMessage;
import org.apache.wicket.feedback.IFeedbackMessageFilter;
import org.apache.wicket.markup.ContainerInfo;
import org.apache.wicket.markup.IMarkupFragment;
import org.apache.wicket.markup.Markup;
import org.apache.wicket.markup.MarkupParser;
import org.apache.wicket.markup.MarkupResourceStream;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.internal.Enclosure;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.link.ResourceLink;
import org.apache.wicket.markup.parser.XmlPullParser;
import org.apache.wicket.markup.parser.XmlTag;
import org.apache.wicket.mock.MockApplication;
import org.apache.wicket.mock.MockPageManager;
import org.apache.wicket.mock.MockRequestParameters;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.page.IPageManager;
import org.apache.wicket.protocol.http.AjaxEnclosureListener;
import org.apache.wicket.protocol.http.IMetaDataBufferingWebResponse;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WicketFilter;
import org.apache.wicket.protocol.http.mock.CookieCollection;
import org.apache.wicket.protocol.http.mock.MockHttpServletRequest;
import org.apache.wicket.protocol.http.mock.MockHttpServletResponse;
import org.apache.wicket.protocol.http.mock.MockHttpSession;
import org.apache.wicket.protocol.http.mock.MockServletContext;
import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
import org.apache.wicket.protocol.http.servlet.ServletWebResponse;
import org.apache.wicket.request.IExceptionMapper;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.IRequestMapper;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.cycle.RequestCycleContext;
import org.apache.wicket.request.handler.render.PageRenderer;
import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
import org.apache.wicket.request.http.WebRequest;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.request.mapper.IRequestMapperDelegate;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.resource.IResource;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.settings.RequestCycleSettings.RenderStrategy;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.lang.Generics;
import org.apache.wicket.util.resource.StringResourceStream;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.visit.IVisit;
import org.apache.wicket.util.visit.IVisitor;
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 <code>WicketTester</code> for example usage. This class can be used as
* is, but JUnit users should use derived class <code>WicketTester</code>.
*
* @see WicketTester
*
* @author Ingram Chen
* @author Juergen Donnerstag
* @author Frank Bille
* @author Igor Vaynberg
*
* @since 1.2.6
*/
@SuppressWarnings("serial")
public class BaseWicketTester
{
/** log. */
private static final Logger log = LoggerFactory.getLogger(BaseWicketTester.class);
private final ServletContext servletContext;
private final WebApplication application;
private final List<MockHttpServletRequest> previousRequests = Generics.newArrayList();
private final List<MockHttpServletResponse> previousResponses = Generics.newArrayList();
private MockHttpSession httpSession;
private boolean followRedirects = true;
private int redirectCount;
private MockHttpServletRequest lastRequest;
private MockHttpServletResponse lastResponse;
/** current request and response */
private MockHttpServletRequest request;
private MockHttpServletResponse response;
/** current session */
private Session session;
/** current request cycle */
private RequestCycle requestCycle;
private Page lastRenderedPage;
private boolean exposeExceptions = true;
private boolean useRequestUrlAsBase = true;
private IRequestHandler forcedHandler;
private IFeedbackMessageFilter originalFeedbackMessageCleanupFilter;
private ComponentInPage componentInPage;
// User may provide request header value any time. They get applied (and reset) upon next
// invocation of processRequest()
private Map<String, String> preHeader;
/**
* Creates <code>WicketTester</code> and automatically create a <code>WebApplication</code>, but
* the tester will have no home page.
*/
public BaseWicketTester()
{
this(new MockApplication());
}
/**
* Creates <code>WicketTester</code> and automatically creates a <code>WebApplication</code>.
*
* @param <C>
* @param homePage
* a home page <code>Class</code>
*/
public <C extends Page> BaseWicketTester(final Class<C> homePage)
{
this(new MockApplication()
{
@Override
public Class<? extends Page> getHomePage()
{
return homePage;
}
});
}
/**
* Creates a <code>WicketTester</code>.
*
* @param application
* a <code>WicketTester</code> <code>WebApplication</code> object
*/
public BaseWicketTester(final WebApplication application)
{
this(application, (MockServletContext)null);
}
/**
* Creates a <code>WicketTester</code>.
*
* @param application
* a <code>WicketTester</code> <code>WebApplication</code> object
* @param servletContextBasePath
* the absolute path on disk to the web application's contents (e.g. war root) - may
* be <code>null</code>
*/
public BaseWicketTester(final WebApplication application, String servletContextBasePath)
{
this(application, new MockServletContext(application, servletContextBasePath));
}
/**
* Creates a <code>WicketTester</code>.
*
* @param application
* a <code>WicketTester</code> <code>WebApplication</code> object
* @param servletCtx
* the servlet context used as backend
*/
public BaseWicketTester(final WebApplication application, final ServletContext servletCtx)
{
this(application, servletCtx, true);
}
/**
* Creates a <code>WicketTester</code>.
*
* @param application
* a <code>WicketTester</code> <code>WebApplication</code> object
* @param init
* force the application to be initialized (default = true)
*/
public BaseWicketTester(final WebApplication application, boolean init)
{
this(application, null, init);
}
/**
* Creates a <code>WicketTester</code>.
*
* @param application
* a <code>WicketTester</code> <code>WebApplication</code> object
* @param servletCtx
* the servlet context used as backend
* @param init
* force the application to be initialized (default = true)
*/
public BaseWicketTester(final WebApplication application, final ServletContext servletCtx,
boolean init)
{
if (!init)
{
Args.notNull(application, "application");
}
servletContext = servletCtx != null ? servletCtx
// If it's provided from the container it's not necessary to mock.
: !init && application.getServletContext() != null ? application.getServletContext()
: new MockServletContext(application, null);
// If using Arquillian and it's configured in a web.xml it'll be provided. If not, mock it.
if (application.getWicketFilter() == null)
{
final FilterConfig filterConfig = new TestFilterConfig();
WicketFilter filter = new WicketFilter()
{
@Override
public FilterConfig getFilterConfig()
{
return filterConfig;
}
};
application.setWicketFilter(filter);
}
httpSession = new MockHttpSession(servletContext);
ThreadContext.detach();
this.application = application;
ThreadContext.setApplication(application);
if (init)
{
if (application.getName() == null)
{
application.setName("WicketTesterApplication-" + UUID.randomUUID());
}
application.setServletContext(servletContext);
// initialize the application
application.initApplication();
}
// We don't expect any changes during testing. In addition we avoid creating
// ModificationWatcher threads tests.
application.getResourceSettings().setResourcePollFrequency(getResourcePollFrequency());
// reconfigure application for the test environment
application.setPageRendererProvider(
new LastPageRecordingPageRendererProvider(application.getPageRendererProvider()));
application.setRequestCycleProvider(
new TestRequestCycleProvider(application.getRequestCycleProvider()));
// set a feedback message filter that will not remove any messages
originalFeedbackMessageCleanupFilter = application.getApplicationSettings()
.getFeedbackMessageCleanupFilter();
application.getApplicationSettings()
.setFeedbackMessageCleanupFilter(IFeedbackMessageFilter.NONE);
IPageManagerProvider pageManagerProvider = newTestPageManagerProvider();
if (pageManagerProvider != null)
{
application.setPageManagerProvider(pageManagerProvider);
}
// create a new session when the old one is invalidated
application.getSessionStore().registerUnboundListener(sessionId -> newSession());
// prepare session
setupNextRequestCycle();
}
/**
* By default Modification Watcher is disabled by default for the tests.
*
* @return the duration between two checks for changes in the resources
*/
protected Duration getResourcePollFrequency()
{
return null;
}
/**
*
* @return page manager provider
*/
protected IPageManagerProvider newTestPageManagerProvider()
{
return new TestPageManagerProvider();
}
/**
* @return last rendered page
*/
public Page getLastRenderedPage()
{
return lastRenderedPage;
}
private void setupNextRequestCycle()
{
request = new MockHttpServletRequest(application, httpSession, servletContext,
servletRequestLocale());
request.setURL(request.getContextPath() + request.getServletPath() + "/");
// assign protocol://host:port to next request unless the last request was ajax
final boolean assignBaseLocation = lastRequest != null &&
lastRequest.getHeader("Wicket-Ajax") == null;
// resume request processing with scheme://host:port from last request
if (assignBaseLocation)
{
request.setScheme(lastRequest.getScheme());
request.setSecure(lastRequest.isSecure());
request.setServerName(lastRequest.getServerName());
request.setServerPort(lastRequest.getServerPort());
}
response = new MockHttpServletResponse(request);
// Preserve response cookies in redirects
// XXX: is this really needed ? Browsers wont do that, but some
// Wicket tests assert that a cookie is in the response,
// even after redirects (see
// org.apache.wicket.util.cookies.SetCookieAndRedirectTest.statefulPage())
// They should assert that the cookie is in the next *request*
if (lastResponse != null)
{
List<Cookie> lastResponseCookies = lastResponse.getCookies();
if (lastResponse.isRedirect())
{
CookieCollection responseCookies = new CookieCollection();
// if the last request is a redirect, all cookies from last response should appear
// in current response
// this call will filter duplicates
responseCookies.addAll(lastResponseCookies);
for (Cookie cookie : responseCookies.allAsList())
{
response.addCookie(cookie);
}
// copy all request cookies from last request to the new request because of redirect
// handling this way, the cookie will be send to the next requested page
if (lastRequest != null)
{
CookieCollection requestCookies = new CookieCollection();
// this call will filter duplicates
requestCookies.addAll(lastRequest.getCookies());
request.addCookies(requestCookies.asList());
}
}
else
{
// if the last response is not a redirect
// - copy last request cookies to collection
// - copy last response cookies to collection
// - set only the not expired cookies to the next request
CookieCollection cookies = new CookieCollection();
if (lastRequest != null)
{
// this call will filter duplicates
cookies.addAll(lastRequest.getCookies());
}
// this call will filter duplicates
cookies.addAll(lastResponseCookies);
request.addCookies(cookies.asList());
}
}
ServletWebRequest servletWebRequest = newServletWebRequest();
requestCycle = application.createRequestCycle(servletWebRequest,
newServletWebResponse(servletWebRequest));
ThreadContext.setRequestCycle(requestCycle);
if (session == null)
{
newSession();
}
}
protected Locale servletRequestLocale()
{
return Locale.getDefault();
}
/**
* Cleans up feedback messages. This usually happens on detach, but is disabled in unit testing
* so feedback messages can be examined.
*/
public void cleanupFeedbackMessages()
{
cleanupFeedbackMessages(originalFeedbackMessageCleanupFilter);
}
/**
* Removes all feedback messages
*/
public void clearFeedbackMessages()
{
cleanupFeedbackMessages(IFeedbackMessageFilter.ALL);
}
/**
* Cleans up feedback messages given the specified filter.
*
* @param filter
* filter used to cleanup messages, accepted messages will be removed
*/
protected void cleanupFeedbackMessages(IFeedbackMessageFilter filter)
{
IVisitor<Component, Void> clearer = new IVisitor<Component, Void>()
{
@Override
public void component(Component component, IVisit<Void> visit)
{
if (component.hasFeedbackMessage()) {
component.getFeedbackMessages().clear(filter);
}
}
};
clearer.component(getLastRenderedPage(), null);
getLastRenderedPage().visitChildren(clearer);
getSession().getFeedbackMessages().clear(filter);
}
/**
* @param servletWebRequest
* @return servlet web response
*/
protected Response newServletWebResponse(final ServletWebRequest servletWebRequest)
{
return new WicketTesterServletWebResponse(servletWebRequest, response);
}
/**
* @return the configured in the user's application web request
*/
private ServletWebRequest newServletWebRequest()
{
return (ServletWebRequest)application.newWebRequest(request, request.getFilterPrefix());
}
/**
*
*/
private void newSession()
{
ThreadContext.setSession(null);
// the following will create a new session and put it in the thread context
session = Session.get();
}
/**
* @return request object
*/
public MockHttpServletRequest getRequest()
{
return request;
}
/**
* @param request
*/
public void setRequest(final MockHttpServletRequest request)
{
this.request = request;
applyRequest();
}
/**
* @param response
*/
public void setLastResponse(final MockHttpServletResponse response)
{
this.lastResponse = response;
}
/**
* @return session
*/
public Session getSession()
{
return session;
}
/**
* Returns {@link HttpSession} for this environment
*
* @return session
*/
public MockHttpSession getHttpSession()
{
return httpSession;
}
/**
* Returns the {@link Application} for this environment.
*
* @return application
*/
public WebApplication getApplication()
{
return application;
}
/**
* Returns the {@link ServletContext} for this environment
*
* @return servlet context
*/
public ServletContext getServletContext()
{
return servletContext;
}
/**
* Destroys the tester. Restores {@link ThreadContext} to state before instance of
* {@link WicketTester} was created.
*/
public void destroy()
{
try
{
ThreadContext.setApplication(application);
application.internalDestroy();
}
finally
{
ThreadContext.detach();
}
}
/**
* @return true, if process was executed successfully
*/
public boolean processRequest()
{
return processRequest(null, null);
}
/**
* Processes the request in mocked Wicket environment.
*
* @param request
* request to process
* @return true, if process was executed successfully
*/
public boolean processRequest(final MockHttpServletRequest request)
{
return processRequest(request, null);
}
/**
* Processes the request in mocked Wicket environment.
*
* @param request
* request to process
* @param forcedRequestHandler
* optional parameter to override parsing the request URL and force
* {@link IRequestHandler}
* @return true, if process was executed successfully
*/
public boolean processRequest(final MockHttpServletRequest request,
final IRequestHandler forcedRequestHandler)
{
return processRequest(request, forcedRequestHandler, false);
}
/**
* @param forcedRequestHandler
* @return true, if process was executed successfully
*/
public boolean processRequest(final IRequestHandler forcedRequestHandler)
{
return processRequest(null, forcedRequestHandler, false);
}
/**
* Process the request. This is a fairly central function and is almost always invoked for
* executing the request.
* <p>
* You may subclass processRequest it, to monitor or change any pre-configured value. Request
* headers can be configured more easily by calling {@link #addRequestHeader(String, String)}.
*
* @param forcedRequest
* Can be null.
* @param forcedRequestHandler
* Can be null.
* @param redirect
* @return true, if process was executed successfully
*/
protected boolean processRequest(final MockHttpServletRequest forcedRequest,
final IRequestHandler forcedRequestHandler, final boolean redirect)
{
if (forcedRequest != null)
{
request = forcedRequest;
}
forcedHandler = forcedRequestHandler;
if (!redirect && getRequest().getHeader("Wicket-Ajax") == null)
{
lastRenderedPage = null;
}
// Add or replace any system provided header entry with the user provided.
if ((request != null) && (preHeader != null))
{
for (Map.Entry<String, String> entry : preHeader.entrySet())
{
if (Strings.isEmpty(entry.getKey()) == false)
{
request.setHeader(entry.getKey(), entry.getValue());
}
}
// Reset the user provided headers
preHeader = null;
}
applyRequest();
requestCycle.scheduleRequestHandlerAfterCurrent(null);
try
{
if (!requestCycle.processRequestAndDetach())
{
return false;
}
}
finally
{
recordRequestResponse();
setupNextRequestCycle();
}
try
{
if (isFollowRedirects() && lastResponse.isRedirect())
{
if (redirectCount++ >= 100)
{
throw new AssertionError("Possible infinite redirect detected. Bailing out.");
}
Url newUrl = Url.parse(lastResponse.getRedirectLocation(),
Charset.forName(request.getCharacterEncoding()));
if (isExternalRedirect(lastRequest.getUrl(), newUrl))
{
// we can't handle external redirects here
// just bail out here and let the user's test code
// check #assertRedirectUrl
return true;
}
if (newUrl.isFull() || newUrl.isContextAbsolute())
{
request.setUrl(newUrl);
final String protocol = newUrl.getProtocol();
if (protocol != null)
{
request.setScheme(protocol);
}
request.setSecure("https".equals(protocol));
if (newUrl.getHost() != null)
{
request.setServerName(newUrl.getHost());
}
if (newUrl.getPort() != null)
{
request.setServerPort(newUrl.getPort());
}
}
else
{
// append redirect URL to current URL (what browser would do)
Url mergedURL = new Url(lastRequest.getUrl().getSegments(),
newUrl.getQueryParameters());
mergedURL.concatSegments(newUrl.getSegments());
request.setUrl(mergedURL);
}
processRequest(null, null, true);
--redirectCount;
}
return true;
}
finally
{
redirectCount = 0;
}
}
/**
* Determine whether a given response contains a redirect leading to an external site (which
* cannot be replicated in WicketTester). This is done by comparing the previous request's
* hostname with the hostname given in the redirect.
*
* @param requestUrl
* request...
* @param newUrl
* ...and the redirect generated in its response
* @return true if there is a redirect and it is external, false otherwise
*/
private boolean isExternalRedirect(Url requestUrl, Url newUrl)
{
String originalHost = requestUrl.getHost();
String redirectHost = newUrl.getHost();
Integer originalPort = requestUrl.getPort();
Integer newPort = newUrl.getPort();
if (originalHost.equals(redirectHost))
{
return false; // identical or both null
}
else if (redirectHost == null)
{
return false; // no new host
}
else if (originalPort.equals(newPort) == false)
{
return true;
}
else
{
return !(redirectHost.equals(originalHost));
}
}
/**
* Allows to set Request header value any time. They'll be applied (add/modify) on process
* execution {@link #processRequest(MockHttpServletRequest, IRequestHandler, boolean)}. They are
* reset immediately after and thus are not re-used for a sequence of requests.
* <p>
* Deletion (not replace) of pre-configured header value can be achieved by subclassing
* {@link #processRequest(MockHttpServletRequest, IRequestHandler, boolean)} and modifying the
* request header directly.
*
* @param key
* @param value
*/
public final void addRequestHeader(final String key, final String value)
{
Args.notEmpty(key, "key");
if (preHeader == null)
{
preHeader = Generics.newHashMap();
}
preHeader.put(key, value);
}
private void recordRequestResponse()
{
lastRequest = request;
setLastResponse(response);
previousRequests.add(request);
previousResponses.add(response);
}
/**
* Renders the page specified by given {@link IPageProvider}. After render the page instance can
* be retrieved using {@link #getLastRenderedPage()} and the rendered document will be available
* in {@link #getLastResponse()}.
*
* Depending on {@link RenderStrategy} invoking this method can mean that a redirect will happen
* before the actual render.
*
* @param pageProvider
* @return last rendered page
*/
public Page startPage(final IPageProvider pageProvider)
{
// should be null for Pages
componentInPage = null;
// prepare request
request.setURL(request.getContextPath() + request.getServletPath() + "/");
IRequestHandler handler = new RenderPageRequestHandler(pageProvider);
// process request
processRequest(request, handler);
// The page rendered
return getLastRenderedPage();
}
/**
* Renders the page.
*
* @see #startPage(IPageProvider)
*
* @param page
* @return Page
*/
@SuppressWarnings("unchecked")
public <T extends Page> T startPage(final T page)
{
return (T)startPage(new PageProvider(page));
}
/**
* Simulates a request to a mounted {@link IResource}
*
* @param resource
* the resource to test
* @return the used {@link ResourceReference} for the simulation
*/
public ResourceReference startResource(final IResource resource)
{
return startResourceReference(new ResourceReference("testResourceReference")
{
private static final long serialVersionUID = 1L;
@Override
public IResource getResource()
{
return resource;
}
});
}
/**
* Simulates a request to a mounted {@link ResourceReference}
*
* @param reference
* the resource reference to test
* @return the tested resource reference
*/
public ResourceReference startResourceReference(final ResourceReference reference)
{
return startResourceReference(reference, null);
}
/**
* Simulates a request to a mounted {@link ResourceReference}
*
* @param reference
* the resource reference to test
* @param pageParameters
* the parameters passed to the resource reference
* @return the tested resource reference
*/
public ResourceReference startResourceReference(final ResourceReference reference,
final PageParameters pageParameters)
{
// prepare request
request.setURL(request.getContextPath() + request.getServletPath() + "/");
IRequestHandler handler = new ResourceReferenceRequestHandler(reference, pageParameters);
// execute request
processRequest(request, handler);
// the reference processed
return reference;
}
/**
* @return last response or <code>null</code>> if no response has been produced yet.
*/
public MockHttpServletResponse getLastResponse()
{
return lastResponse;
}
/**
* The last response as a string when a page is tested via {@code startPage()} methods.
* <p>
* In case the processed component was not a {@link Page} then the automatically created page
* markup gets removed. If you need the whole returned markup in this case use
* {@link #getLastResponse()}{@link MockHttpServletResponse#getDocument() .getDocument()}
* </p>
*
* @return last response as String.
*/
public String getLastResponseAsString()
{
String response = lastResponse.getDocument();
// null, if a Page was rendered last
if (componentInPage == null)
{
return response;
}
// remove the markup for the auto-generated page. leave just component's markup
int end = response.lastIndexOf("</body>");
if (end > -1)
{
int start = response.indexOf("<body>") + "<body>".length();
response = response.substring(start, end);
}
return response;
}
/**
* This method tries to parse the last response to return the encoded base URL and will throw an
* exception if there none was encoded.
*
* @return Wicket-Ajax-BaseURL set on last response by {@link AbstractDefaultAjaxBehavior}
* @throws IOException
* @throws ParseException
*/
public String getWicketAjaxBaseUrlEncodedInLastResponse()
throws IOException, ParseException
{
XmlPullParser parser = new XmlPullParser();
parser.parse(getLastResponseAsString());
XmlTag tag;
while ((tag = parser.nextTag()) != null)
{
if (tag.isOpen() && tag.getName().equals("script") &&
"wicket-ajax-base-url".equals(tag.getAttribute("id")))
{
parser.next();
return parser.getString().toString().split("\\\"")[1];
}
}
fail("Last response has no AJAX base URL set by AbstractDefaultAjaxBehavior.");
return null;
}
/**
* @return list of prior requests
*/
public List<MockHttpServletRequest> getPreviousRequests()
{
return previousRequests;
}
/**
* @return list of prior responses
*/
public List<MockHttpServletResponse> getPreviousResponses()
{
return previousResponses;
}
/**
* Sets whether responses with redirects will be followed automatically.
*
* @param followRedirects
*/
public void setFollowRedirects(boolean followRedirects)
{
this.followRedirects = followRedirects;
}
/**
* @return <code>true</code> if redirect responses will be followed automatically,
* <code>false</code> otherwise.
*/
public boolean isFollowRedirects()
{
return followRedirects;
}
/**
* Encodes the {@link IRequestHandler} to {@link Url}. It should be safe to call this method
* outside request thread as log as no registered {@link IRequestMapper} requires a
* {@link RequestCycle}.
*
* @param handler
* @return {@link Url} for handler.
*/
public Url urlFor(final IRequestHandler handler)
{
Url url = application.getRootRequestMapper().mapHandler(handler);
return transform(url);
}
/**
* @param link
* @return url for Link
*/
public String urlFor(Link<?> link)
{
Args.notNull(link, "link");
Url url = Url.parse(link.urlForListener(new PageParameters()).toString());
return transform(url).toString();
}
/**
* Simulates processing URL that invokes an {@link IRequestListener} on a component.
*
* After the listener is invoked the page containing the component will be rendered (with an
* optional redirect - depending on {@link RenderStrategy}).
*
* @param component
*/
public void executeListener(final Component component)
{
Args.notNull(component, "component");
// there are two ways to do this. RequestCycle could be forced to call the handler
// directly but constructing and parsing the URL increases the chance of triggering bugs
Page page = component.getPage();
PageAndComponentProvider pageAndComponentProvider = new PageAndComponentProvider(page,
component);
IRequestHandler handler = null;
if (page.isPageStateless() || (page.isBookmarkable() && page.wasCreatedBookmarkable()))
{
handler = new BookmarkableListenerRequestHandler(pageAndComponentProvider);
}
else
{
handler = new ListenerRequestHandler(pageAndComponentProvider);
}
Url url = urlFor(handler);
request.setUrl(url);
// Process the request
processRequest(request, null);
}
/**
* Simulates invoking an {@link IRequestListener} on a component. As opposed to the
* {@link #executeListener(Component)} method, current request/response objects will be used
*
* After the listener is invoked the page containing the component will be rendered (with an
* optional redirect - depending on {@link RenderStrategy}).
*
* @param component
*/
public void invokeListener(final Component component)
{
Args.notNull(component, "component");
// there are two ways to do this. RequestCycle could be forced to call the handler
// directly but constructing and parsing the URL increases the chance of triggering bugs
IRequestHandler handler = new ListenerRequestHandler(
new PageAndComponentProvider(component.getPage(), component));
processRequest(handler);
}
/**
* Simulates invoking an {@link IRequestListener} on a component. As opposed to the
* {@link #executeListener(Component)} method, current request/response objects will be used
*
* After the listener is invoked the page containing the component will be rendered (with an
* optional redirect - depending on {@link RenderStrategy}).
*
* @param component
* @param behavior
*/
public void invokeListener(Component component, final Behavior behavior)
{
Args.notNull(component, "component");
Args.notNull(behavior, "behavior");
// there are two ways to do this. RequestCycle could be forced to call the handler
// directly but constructing and parsing the URL increases the chance of triggering bugs
IRequestHandler handler = new ListenerRequestHandler(
new PageAndComponentProvider(component.getPage(), component),
component.getBehaviorId(behavior));
processRequest(handler);
}
/**
* Builds and processes a request suitable for executing an <code>AbstractAjaxBehavior</code>.
*
* @param behavior
* an <code>AbstractAjaxBehavior</code> to execute
*/
public void executeBehavior(final AbstractAjaxBehavior behavior)
{
Args.notNull(behavior, "behavior");
Url url = Url.parse(behavior.getCallbackUrl().toString(),
Charset.forName(request.getCharacterEncoding()));
transform(url);
request.setUrl(url);
request.addHeader(WebRequest.HEADER_ORIGIN, createOriginHeader());
request.addHeader(WebRequest.HEADER_AJAX_BASE_URL, url.toString());
request.addHeader(WebRequest.HEADER_AJAX, "true");
if (behavior instanceof AjaxFormSubmitBehavior)
{
AjaxFormSubmitBehavior formSubmitBehavior = (AjaxFormSubmitBehavior)behavior;
Form<?> form = formSubmitBehavior.getForm();
getRequest().setUseMultiPartContentType(form.isMultiPart());
serializeFormToRequest(form);
// mark behavior's component as the form submitter,
String name = Form.getRootFormRelativeId(
new PropertyModel<Component>(behavior, "component").getObject());
if (!request.getPostParameters().getParameterNames().contains(name))
{
request.getPostParameters().setParameterValue(name, "marked");
}
}
processRequest();
}
/**
* Build value to Origin header based on RequestCycle Url
*
* @return Origin header
*/
protected String createOriginHeader()
{
Url url = RequestCycle.get().getRequest().getUrl();
return url.getProtocol() + "://" + url.getHost() + ":" + url.getPort();
}
/**
*
* @param link
* @return Url
*/
public Url urlFor(final AjaxLink<?> link)
{
AbstractAjaxBehavior behavior = WicketTesterHelper.findAjaxEventBehavior(link, "click");
Url url = Url.parse(behavior.getCallbackUrl().toString(),
Charset.forName(request.getCharacterEncoding()));
return transform(url);
}
/**
*
* @param url
*/
public void executeAjaxUrl(final Url url)
{
Args.notNull(url, "url");
transform(url);
request.setUrl(url);
request.addHeader("Wicket-Ajax-BaseURL", url.toString());
request.addHeader("Wicket-Ajax", "true");
processRequest();
}
/**
* Renders a <code>Page</code> from its default constructor.
*
* @param <C>
* @param pageClass
* a test <code>Page</code> class with default constructor
* @return the rendered <code>Page</code>
*/
public final <C extends Page> C startPage(final Class<C> pageClass)
{
return startPage(pageClass, null);
}
/**
* Renders a <code>Page</code> from its default constructor.
*
* @param <C>
* @param pageClass
* a test <code>Page</code> class with default constructor
* @param parameters
* the parameters to use for the class.
* @return the rendered <code>Page</code>
*/
@SuppressWarnings("unchecked")
public final <C extends Page> C startPage(final Class<C> pageClass,
final PageParameters parameters)
{
Args.notNull(pageClass, "pageClass");
// must be null for Pages
componentInPage = null;
// prepare the request
request.setUrl(application.getRootRequestMapper().mapHandler(
new BookmarkablePageRequestHandler(new PageProvider(pageClass, parameters))));
// process the request
processRequest();
// The last rendered page
return (C)getLastRenderedPage();
}
/**
* Creates a {@link FormTester} for the <code>Form</code> at a given path, and fills all child
* {@link org.apache.wicket.markup.html.form.FormComponent}s with blank <code>String</code>s.
*
* @param path
* path to <code>FormComponent</code>
* @return a <code>FormTester</code> instance for testing the <code>Form</code>
* @see #newFormTester(String, boolean)
*/
public FormTester newFormTester(final String path)
{
return newFormTester(path, true);
}
/**
* Creates a {@link FormTester} for the <code>Form</code> at a given path.
*
* @param path
* path to <code>FormComponent</code>
* @param fillBlankString
* specifies whether to fill all child <code>FormComponent</code>s with blank
* <code>String</code>s
* @return a <code>FormTester</code> instance for testing the <code>Form</code>
* @see FormTester
*/
public FormTester newFormTester(final String path, final boolean fillBlankString)
{
return new FormTester(path, (Form<?>)getComponentFromLastRenderedPage(path), this,
fillBlankString);
}
/**
* Process a component. A web page will be automatically created with the markup created in
* {@link #createPageMarkup(String)}.
* <p>
* <strong>Note</strong>: the instantiated component will have an auto-generated id. To reach
* any of its children use their relative path to the component itself. For example if the
* started component has a child a Link component with id "link" then after starting the
* component you can click it with: <code>tester.clickLink("link")</code>
* </p>
*
* @param <C>
* the type of the component
* @param componentClass
* the class of the component to be tested
* @return The component processed
* @see #startComponentInPage(org.apache.wicket.Component)
*/
public final <C extends Component> C startComponentInPage(final Class<C> componentClass)
{
return startComponentInPage(componentClass, null);
}
/**
* Process a component. A web page will be automatically created with the {@code pageMarkup}
* provided. In case pageMarkup is null, the markup will be automatically created with
* {@link #createPageMarkup(String)}.
* <p>
* <strong>Note</strong>: the instantiated component will have an auto-generated id. To reach
* any of its children use their relative path to the component itself. For example if the
* started component has a child a Link component with id "link" then after starting the
* component you can click it with: <code>tester.clickLink("link")</code>
* </p>
*
* @param <C>
* the type of the component
*
* @param componentClass
* the class of the component to be tested
* @param pageMarkup
* the markup for the Page that will be automatically created. May be {@code null}.
* @return The component processed
*/
public final <C extends Component> C startComponentInPage(final Class<C> componentClass,
final IMarkupFragment pageMarkup)
{
Args.notNull(componentClass, "componentClass");
// Create the component instance from the class
C comp = null;
try
{
Constructor<C> c = componentClass.getConstructor(String.class);
comp = c.newInstance(ComponentInPage.ID);
}
catch (Exception e)
{
log.error(e.getMessage(), e);
fail(String.format("Cannot instantiate component with type '%s' because of '%s'",
componentClass.getName(), e.getMessage()));
}
// process the component
C started = startComponentInPage(comp, pageMarkup);
componentInPage.isInstantiated = true;
return started;
}
/**
* Process a component. A web page will be automatically created with markup created by the
* {@link #createPageMarkup(String)}.
* <p>
* <strong>Note</strong>: the component id is set by the user. To reach any of its children use
* this id + their relative path to the component itself. For example if the started component
* has id <em>compId</em> and a Link child component component with id "link" then after
* starting the component you can click it with: <code>tester.clickLink("compId:link")</code>
* </p>
*
* @param <C>
* the type of the component
* @param component
* the component to be tested
* @return The component processed
* @see #startComponentInPage(Class)
*/
public final <C extends Component> C startComponentInPage(final C component)
{
return startComponentInPage(component, null);
}
/**
* Process a component. A web page will be automatically created with the {@code pageMarkup}
* provided. In case {@code pageMarkup} is null, the markup will be automatically created with
* {@link #createPageMarkup(String)}.
* <p>
* <strong>Note</strong>: the component id is set by the user. To reach any of its children use
* this id + their relative path to the component itself. For example if the started component
* has id <em>compId</em> and a Link child component component with id "link" then after
* starting the component you can click it with: <code>tester.clickLink("compId:link")</code>
* </p>
*
* @param <C>
* the type of the component
* @param component
* the component to be tested
* @param pageMarkup
* the markup for the Page that will be automatically created. May be {@code null}.
* @return The component processed
*/
public final <C extends Component> C startComponentInPage(final C component,
IMarkupFragment pageMarkup)
{
Args.notNull(component, "component");
// Create a page object and assign the markup
Page page = createPage();
if (page == null)
{
fail("The automatically created page should not be null.");
}
// Automatically create the page markup if not provided
if (pageMarkup == null)
{
String markup = createPageMarkup(component.getId());
if (markup == null)
{
fail("The markup for the automatically created page should not be null.");
}
try
{
// set a ContainerInfo to be able to use HtmlHeaderContainer so header contribution
// still work. WICKET-3700
ContainerInfo containerInfo = new ContainerInfo(page);
MarkupResourceStream markupResourceStream = new MarkupResourceStream(
new StringResourceStream(markup), containerInfo, page.getClass());
MarkupParser markupParser = getApplication().getMarkupSettings()
.getMarkupFactory()
.newMarkupParser(markupResourceStream);
pageMarkup = markupParser.parse();
}
catch (Exception e)
{
String errorMessage = "Error while parsing the markup for the autogenerated page: " +
e.getMessage();
log.error(errorMessage, e);
fail(errorMessage);
}
}
if (page instanceof StartComponentInPage)
{
((StartComponentInPage)page).setPageMarkup(pageMarkup);
}
else
{
page.setMarkup(pageMarkup);
}
// Add the child component
page.add(component);
// Process the page
startPage(page);
componentInPage = new ComponentInPage();
componentInPage.component = component;
return component;
}
/**
* Creates the markup that will be used for the automatically created {@link Page} that will be
* used to test a component with {@link #startComponentInPage(Class, IMarkupFragment)}
*
* @param componentId
* the id of the component to be tested
* @return the markup for the {@link Page} as {@link String}. Cannot be {@code null}.
*/
protected String createPageMarkup(final String componentId)
{
return "<html><head></head><body><span wicket:id='" + componentId +
"'></span></body></html>";
}
/**
* Creates a {@link Page} to test a component with
* {@link #startComponentInPage(Component, IMarkupFragment)}
*
* @return a {@link Page} which will contain the component under test as a single child
*/
protected Page createPage()
{
return new StartComponentInPage();
}
public Component getComponentFromLastRenderedPage(String path,
boolean wantVisibleInHierarchy)
{
return getComponentFromLastRenderedPage(path, wantVisibleInHierarchy, true);
}
/**
* Gets the component with the given path from last rendered page. This method fails in case the
* component couldn't be found.
*
* @param path
* Path to component
* @param wantVisibleInHierarchy
* if true component needs to be visible in hierarchy else {@code null} is returned
* @return The component at the path
* @see org.apache.wicket.MarkupContainer#get(String)
*/
public Component getComponentFromLastRenderedPage(String path,
boolean wantVisibleInHierarchy, boolean failOnAbsent)
{
if (componentInPage != null && componentInPage.isInstantiated)
{
String componentIdPageId = componentInPage.component.getId() + ':';
if (path.startsWith(componentIdPageId) == false)
{
path = componentIdPageId + path;
}
}
Component component = getLastRenderedPage().get(path);
if (component == null)
{
if (failOnAbsent)
{
fail("path: '" + path + "' does not exist for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
}
return null;
}
if (!wantVisibleInHierarchy || component.isVisibleInHierarchy())
{
return component;
}
// Not found or not visible
return null;
}
/**
* 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)
{
return getComponentFromLastRenderedPage(path, true);
}
/**
* assert the text of <code>Label</code> component.
*
* @param path
* path to <code>Label</code> component
* @param expectedLabelText
* expected label text
* @return a <code>Result</code>
*/
public Result hasLabel(String path, String expectedLabelText)
{
Label label = (Label)getComponentFromLastRenderedPage(path);
return isEqual(expectedLabelText, label.getDefaultModelObjectAsString());
}
/**
* assert component class
*
* @param <C>
*
* @param path
* path to component
* @param expectedComponentClass
* expected component class
* @return a <code>Result</code>
*/
public <C extends Component> Result isComponent(String path, Class<C> expectedComponentClass)
{
Component component = assertExists(path);
return isTrue(
"component '" + Classes.name(component.getClass()) + "' is not of type: " +
Classes.name(expectedComponentClass),
expectedComponentClass.isAssignableFrom(component.getClass()));
}
/**
* assert component visible.
*
* @param path
* path to component
* @return a <code>Result</code>
*/
public Result isVisible(final String path)
{
final Result result;
Component component = getComponentFromLastRenderedPage(path, false);
if (component == null)
{
result = Result.fail("path: '" + path + "' does not exist for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
}
else
{
result = isTrue("component '" + path + "' is not visible",
component.isVisibleInHierarchy());
}
return result;
}
/**
* assert component invisible.
*
* @param path
* path to component
* @return a <code>Result</code>
*/
public Result isInvisible(final String path)
{
final Result result;
Component component = getComponentFromLastRenderedPage(path, false);
if (component == null)
{
result = Result.fail("path: '" + path + "' does not exist for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
}
else
{
result = isFalse("component '" + path + "' is visible",
component.isVisibleInHierarchy());
}
return result;
}
/**
* assert component enabled.
*
* @param path
* path to component
* @return a <code>Result</code>
*/
public Result isEnabled(final String path)
{
Component component = assertExists(path);
return isTrue("component '" + path + "' is disabled", component.isEnabledInHierarchy());
}
/**
* assert component disabled.
*
* @param path
* path to component
* @return a <code>Result</code>
*/
public Result isDisabled(final String path)
{
Component component = assertExists(path);
return isFalse("component '" + path + "' is enabled", component.isEnabledInHierarchy());
}
public Component assertExists(final String path)
{
Component component = getComponentFromLastRenderedPage(path);
if (component == null)
{
fail("path: '" + path + "' does not exist for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
return null;
}
return component;
}
public void assertNotExists(final String path)
{
Component component = getComponentFromLastRenderedPage(path, true, false);
if (component != null)
{
fail("path: '" + path + "' does exists for page: " +
Classes.simpleName(getLastRenderedPage().getClass()));
}
}
private FormComponent<?> assertFormComponent(final String path)
{
Component component = assertExists(path);
if (component instanceof FormComponent<?> == false)
{
fail("path: '" + path + "' is not a form component");
return null;
}
return (FormComponent<?>)component;
}
/**
* assert component required.
*
* @param path
* path to component
* @return a <code>Result</code>
*/
public Result isRequired(String path)
{
FormComponent<?> formComponent = assertFormComponent(path);
return isRequired(formComponent);
}
/**
* assert component required.
*
* @param component
* a form component
* @return a <code>Result</code>
*/
public Result isRequired(FormComponent<?> component)
{
return isTrue("component '" + component + "' is not required", component.isRequired());
}
/**
* Asserts that a component is not required.
*
* @param path
* path to component
* @return a <code>Result</code>
*/
public Result isNotRequired(String path)
{
FormComponent<?> formComponent = assertFormComponent(path);
return isNotRequired(formComponent);
}
/**
* Asserts that a component is not required.
*
* @param component
* a form component
* @return a <code>Result</code>
*/
public Result isNotRequired(FormComponent<?> component)
{
return isFalse("component '" + component + "' is required", component.isRequired());
}
/**
* assert the content of last rendered page contains(matches) regex pattern.
*
* @param pattern
* reqex pattern to match
* @return a <code>Result</code>
*/
public Result ifContains(String pattern)
{
return isTrue("pattern '" + pattern + "' not found in:\n" + getLastResponseAsString(),
getLastResponseAsString().matches("(?s).*" + pattern + ".*"));
}
/**
* assert the content of last rendered page contains(matches) regex pattern.
*
* @param pattern
* reqex pattern to match
* @return a <code>Result</code>
*/
public Result ifContainsNot(String pattern)
{
return isFalse("pattern '" + pattern + "' found",
getLastResponseAsString().matches("(?s).*" + pattern + ".*"));
}
/**
* Click the {@link Link} in the last rendered Page.
* <p>
* Simulate that AJAX is enabled.
*
* @see WicketTester#clickLink(String, boolean)
* @param path
* Click the <code>Link</code> in the last rendered Page.
*/
public void clickLink(String path)
{
clickLink(path, true);
}
/**
* Click the {@link Link} in the last rendered Page.
* <p>
* This method also works for {@link AjaxLink}, {@link AjaxFallbackLink} and
* {@link AjaxSubmitLink}.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 <code>Link</code> 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, true);
// 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.");
}
List<AjaxEventBehavior> behaviors = WicketTesterHelper
.findAjaxEventBehaviors(linkComponent, "click");
for (AjaxEventBehavior behavior : behaviors)
{
executeBehavior(behavior);
}
}
// 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;
String pageRelativePath = link.getInputName();
request.getPostParameters().setParameterValue(pageRelativePath, "x");
submitAjaxFormSubmitBehavior(link,
(AjaxFormSubmitBehavior)WicketTesterHelper.findAjaxEventBehavior(link, "click"));
}
// if the link is an IAjaxLink, use it (do check if AJAX is expected)
else if (isAjax &&
(linkComponent instanceof IAjaxLink || linkComponent instanceof AjaxFallbackLink))
{
List<AjaxEventBehavior> behaviors = WicketTesterHelper
.findAjaxEventBehaviors(linkComponent, "click");
for (AjaxEventBehavior behavior : behaviors)
{
executeBehavior(behavior);
}
}
/*
* 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();
request.getPostParameters().setParameterValue(pageRelativePath, "x");
serializeFormToRequest(submitLink.getForm());
submitForm(submitLink.getForm().getPageRelativePath());
}
else if (linkComponent instanceof ExternalLink)
{
ExternalLink externalLink = (ExternalLink)linkComponent;
String href = externalLink.getDefaultModelObjectAsString();
try
{
getResponse().sendRedirect(href);
recordRequestResponse();
setupNextRequestCycle();
}
catch (IOException iox)
{
throw new WicketRuntimeException("An error occurred while redirecting to: " + href,
iox);
}
}
// 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
{
Method getParametersMethod = BookmarkablePageLink.class
.getDeclaredMethod("getPageParameters", (Class<?>[])null);
getParametersMethod.setAccessible(true);
PageParameters parameters = (PageParameters)getParametersMethod
.invoke(bookmarkablePageLink, (Object[])null);
startPage(bookmarkablePageLink.getPageClass(), parameters);
}
catch (Exception e)
{
throw new WicketRuntimeException("Internal error in WicketTester. " +
"Please report this in Wicket's Issue Tracker.", e);
}
}
else if (link instanceof ResourceLink)
{
try
{
Method getURL = ResourceLink.class.getDeclaredMethod("getURL");
getURL.setAccessible(true);
CharSequence url = (CharSequence)getURL.invoke(link);
executeUrl(url.toString());
}
catch (Exception x)
{
throw new RuntimeException("An error occurred while clicking on a ResourceLink",
x);
}
}
else
{
executeListener(link);
}
}
// The link requires AJAX
else if (linkComponent instanceof IAjaxLink && isAjax == false)
{
fail("Link " + path + "is an IAjaxLink and will " +
"not be invoked when AJAX (javascript) is disabled.");
}
else
{
fail("Link " + path + " is not an instance of AbstractLink or IAjaxLink");
}
}
/**
* Submit the given form in the last rendered {@link Page}
* <p>
* <strong>Note</strong>: Form request parameters have to be set explicitly.
*
* @param form
* path to component
*/
public void submitForm(Form<?> form)
{
submitForm(form.getPageRelativePath());
}
/**
* Submits the {@link Form} in the last rendered {@link Page}.
* <p>
* <strong>Note</strong>: Form request parameters have to be set explicitely.
*
* @param path
* path to component
*/
public void submitForm(String path)
{
Form<?> form = (Form<?>)getComponentFromLastRenderedPage(path);
Url url = Url.parse(form.getRootForm().urlForListener(new PageParameters()).toString(),
Charset.forName(request.getCharacterEncoding()));
// make url absolute
transform(url);
request.setUrl(url);
processRequest();
}
/**
* make url suitable for wicket tester use. usually this involves stripping any leading ..
* segments to make the url absolute
*
* @param url
* @return Url
*/
private Url transform(final Url url)
{
while (url.getSegments().size() > 0 &&
(url.getSegments().get(0).equals("..") || url.getSegments().get(0).equals(".")))
{
url.getSegments().remove(0);
}
return url;
}
/**
* Asserts the last rendered <code>Page</code> class.
*
* @param <C>
* @param expectedRenderedPageClass
* expected class of last rendered page
* @return a <code>Result</code>
*/
public <C extends Page> Result isRenderedPage(Class<C> expectedRenderedPageClass)
{
Args.notNull(expectedRenderedPageClass, "expectedRenderedPageClass");
Page page = getLastRenderedPage();
if (page == null)
{
return Result.fail("page was null");
}
if (!expectedRenderedPageClass.isAssignableFrom(page.getClass()))
{
return Result.fail(String.format("classes not the same, expected '%s', current '%s'",
expectedRenderedPageClass, page.getClass()));
}
return Result.pass();
}
/**
* Asserts last rendered <code>Page</code> against an expected HTML document.
* <p>
* Use <code>-Dwicket.replace.expected.results=true</code> to automatically replace the expected
* output file.
* </p>
*
* @param pageClass
* used to load the <code>File</code> (relative to <code>clazz</code> package)
* @param filename
* expected output <code>File</code> name
* @throws Exception
*/
public void assertResultPage(final Class<?> pageClass, final String filename) throws Exception
{
// Validate the document
String document = getLastResponseAsString();
DiffUtil.validatePage(document, pageClass, filename, true);
}
/**
* Asserts last rendered <code>Page</code> against an expected HTML document as a
* <code>String</code>.
*
* @param expectedDocument
* expected output
* @return a <code>Result</code>
*/
public Result isResultPage(final String expectedDocument) {
// Validate the document
String document = getLastResponseAsString();
return isTrue("expected rendered page equals", document.equals(expectedDocument));
}
/**
* Asserts no error-level feedback messages.
*
* @return a <code>Result</code>
* @see #hasNoFeedbackMessage(int)
*/
public Result hasNoErrorMessage()
{
return hasNoFeedbackMessage(FeedbackMessage.ERROR);
}
/**
* Asserts no info-level feedback messages.
*
* @return a <code>Result</code>
* @see #hasNoFeedbackMessage(int)
*/
public Result hasNoInfoMessage()
{
return hasNoFeedbackMessage(FeedbackMessage.INFO);
}
/**
* Asserts there are no feedback messages with the given level.
*
* @param level
* the level of the feedback message
* @return a <code>Result</code>
*/
public Result hasNoFeedbackMessage(int level)
{
List<Serializable> messages = getMessages(level);
return isTrue(String.format("expected no %s message, but contains\n%s",
new FeedbackMessage(null, "", level).getLevelAsString().toLowerCase(Locale.ROOT),
WicketTesterHelper.asLined(messages)), messages.isEmpty());
}
/**
* Retrieves <code>FeedbackMessages</code>.
*
* @param level
* level of feedback message, for example:
* <code>FeedbackMessage.DEBUG or FeedbackMessage.INFO.. etc</code>
* @return <code>List</code> of messages (as <code>String</code>s)
* @see FeedbackMessage
*/
public List<Serializable> getMessages(final int level)
{
List<FeedbackMessage> messages = getFeedbackMessages(
new ExactLevelFeedbackMessageFilter(level));
List<Serializable> actualMessages = Generics.newArrayList();
for (FeedbackMessage message : messages)
{
actualMessages.add(message.getMessage());
}
return actualMessages;
}
/**
* Retrieves <code>FeedbackMessages</code>.
*
* @param filter
* A filter that decides which messages to collect
* @return <code>List</code> of messages (as <code>String</code>s)
* @see FeedbackMessage
*/
public List<FeedbackMessage> getFeedbackMessages(final IFeedbackMessageFilter filter)
{
return new FeedbackCollector(getLastRenderedPage(), true).collect(filter);
}
/**
* Dumps the source of last rendered <code>Page</code>.
*/
public void dumpPage()
{
log.info(getLastResponseAsString());
}
/**
* Dumps the <code>Component</code> trees.
*/
public void debugComponentTrees()
{
debugComponentTrees("");
}
/**
* Dumps the <code>Component</code> trees to log. Show only the <code>Component</code>s whose
* paths contain the filter <code>String</code>.
*
* @param filter
* a filter <code>String</code>
*/
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 + "]",
obj.isEnabled ? "E" : "-", obj.isVisible ? "V" : "-");
}
}
}
/**
* Tests that a <code>Component</code> has been added to a <code>AjaxRequestTarget</code>, using
* {@link org.apache.wicket.ajax.AjaxRequestTarget#add(org.apache.wicket.Component...)}. This
* method actually tests that a <code>Component</code> is on the Ajax response sent back to the
* client.
* <p>
* PLEASE NOTE! This method doesn't actually insert the <code>Component</code> 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 <code>Component</code> to test
* @return a <code>Result</code>
*/
public Result isComponentOnAjaxResponse(final 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
String componentInfo = component.toString();
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. " +
componentInfo;
result = isTrue(failMessage, component.getOutputMarkupPlaceholderTag());
if (result.wasFailed())
{
return result;
}
}
// Get the AJAX response
String ajaxResponse = getLastResponseAsString();
// 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() or " +
"#executeAjaxEvent(), before using this assertion method";
boolean isAjaxResponse = Pattern
.compile("^<\\?xml version=\"1.0\" encoding=\".*?\"\\?><ajax-response>")
.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. " + componentInfo;
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).*<component id=\"" + markupId + "\"[^>]*?>.*");
failMessage = "Component wasn't found in the AJAX response. " + componentInfo;
result = isTrue(failMessage, isComponentInAjaxResponse);
if (!result.wasFailed())
{
return result;
}
// Check if the component has been included as part of an enclosure render
Enclosure enclosure = getLastRenderedPage().visitChildren(Enclosure.class,
(Enclosure enc, IVisit<Enclosure> visit) -> {
if (AjaxEnclosureListener.isControllerOfEnclosure(component, enc))
{
visit.stop(enc);
}
});
if (enclosure == null)
{
return result;
}
failMessage = "Component's enclosure was not found in the AJAX response. " + enclosure;
boolean isEnclosureInAjaxResponse = !isComponentOnAjaxResponse(enclosure).wasFailed();
return isTrue(failMessage, isEnclosureInAjaxResponse);
}
/**
* Simulates the firing of an Ajax event.
*
* @see #executeAjaxEvent(Component, String)
*
* @since 1.2.3
* @param componentPath
* the <code>Component</code> path
* @param event
* the event which we simulate being fired. If <code>event</code> is
* <code>null</code>, the test will fail.
*/
public void executeAjaxEvent(final String componentPath, final 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<Component, Void>()
{
@Override
public void component(final Component component, final IVisit<Void> visit)
{
internalExecuteAllTimerBehaviors(component);
}
});
}
private void internalExecuteAllTimerBehaviors(final Component component)
{
List<AbstractAjaxTimerBehavior> behaviors = component
.getBehaviors(AbstractAjaxTimerBehavior.class);
for (AbstractAjaxTimerBehavior timer : behaviors)
{
checkUsability(component, true);
if (!timer.isStopped())
{
if (log.isDebugEnabled())
{
log.debug("Triggering AjaxSelfUpdatingTimerBehavior: {}",
component.getClassRelativePath());
}
executeBehavior(timer);
}
}
}
/**
* Simulates the firing of an Ajax event. You add an Ajax event to a <code>Component</code> by
* using:
*
* <pre>
* ...
* component.add(new AjaxEventBehavior(&quot;ondblclick&quot;) {
* public void onEvent(AjaxRequestTarget) {}
* });
* ...
* </pre>
*
* You can then test that the code inside <code>onEvent</code> actually does what it's supposed
* to, using the <code>WicketTester</code>:
*
* <pre>
* ...
* tester.executeAjaxEvent(component, &quot;ondblclick&quot;);
* // Test that the code inside onEvent is correct.
* ...
* </pre>
*
* This also works with <code>AjaxFormSubmitBehavior</code>, where it will "submit" the
* <code>Form</code> before executing the command.
* <p>
* PLEASE NOTE! This method doesn't actually insert the <code>Component</code> in the client DOM
* tree, using JavaScript.
*
* @param component
* the <code>Component</code> that has the <code>AjaxEventBehavior</code> we want to
* test. If the <code>Component</code> is <code>null</code>, the test will fail.
* @param event
* the event to simulate being fired. If <code>event</code> is <code>null</code>, the
* test will fail.
*/
public void executeAjaxEvent(final Component component, final String event)
{
Args.notNull(component, "component");
Args.notNull(event, "event");
checkUsability(component, true);
List<AjaxEventBehavior> ajaxEventBehaviors = WicketTesterHelper
.findAjaxEventBehaviors(component, event);
for (AjaxEventBehavior ajaxEventBehavior : ajaxEventBehaviors)
{
executeBehavior(ajaxEventBehavior);
}
}
/**
* Retrieves a <code>TagTester</code> based on a <code>wicket:id</code>. If more
* <code>Component</code>s exist with the same <code>wicket:id</code> in the markup, only the
* first one is returned.
*
* @param wicketId
* the <code>wicket:id</code> to search for
* @return the <code>TagTester</code> for the tag which has the given <code>wicket:id</code>
*/
public TagTester getTagByWicketId(String wicketId)
{
return TagTester.createTagByAttribute(getLastResponseAsString(), "wicket:id", wicketId);
}
/**
* Modified version of BaseWicketTester#getTagByWicketId(String) that returns all matching tags
* instead of just the first.
*
* @param wicketId
* @return List of Tags
*/
public List<TagTester> getTagsByWicketId(String wicketId)
{
return TagTester.createTagsByAttribute(getLastResponseAsString(), "wicket:id", wicketId,
false);
}
/**
* Retrieves a <code>TagTester</code> based on an DOM id. If more <code>Component</code>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 <code>TagTester</code> for the tag which has the given DOM id
*/
public TagTester getTagById(String id)
{
return TagTester.createTagByAttribute(getLastResponseAsString(), "id", id);
}
/**
* Helper method for all the places where an Ajax call should submit an associated
* <code>Form</code>.
*
* @param component
* The component the behavior is attached to
* @param behavior
* The <code>AjaxFormSubmitBehavior</code> with the <code>Form</code> to "submit"
*/
private void submitAjaxFormSubmitBehavior(final Component component,
AjaxFormSubmitBehavior behavior)
{
// The form that needs to be "submitted".
Form<?> form = behavior.getForm();
assertNotNull(form, "No form attached to the submitlink.");
checkUsability(form, true);
serializeFormToRequest(form);
executeBehavior(behavior);
}
/**
* Puts all not already scheduled (e.g. via {@link FormTester#setValue(String, String)}) form
* component values in the post parameters for the next form submit
*
* @param form
* the {@link Form} which components should be submitted
*/
private void serializeFormToRequest(final Form<?> form)
{
final MockRequestParameters postParameters = request.getPostParameters();
final Set<String> currentParameterNamesSet = postParameters.getParameterNames();
form.visitFormComponents(new IVisitor<FormComponent<?>, Void>()
{
@Override
public void component(final FormComponent<?> formComponent, final IVisit<Void> visit)
{
final String inputName = formComponent.getInputName();
if (!currentParameterNamesSet.contains(inputName))
{
String[] values = FormTester.getInputValue(formComponent);
for (String value : values)
{
postParameters.addParameterValue(inputName, value);
}
}
}
});
}
/**
* Retrieves the content type from the response header.
*
* @return the content type from the response header
*/
public String getContentTypeFromResponseHeader()
{
String contentType = getLastResponse().getContentType();
assertNotNull("No Content-Type header found", contentType);
return contentType;
}
/**
* Retrieves the content length from the response header.
*
* @return the content length from the response header
*/
public int getContentLengthFromResponseHeader()
{
String contentLength = getLastResponse().getHeader("Content-Length");
assertNotNull("No Content-Length header found", contentLength);
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 getLastResponse().getHeader("Last-Modified");
}
/**
* Retrieves the content disposition from the response header.
*
* @return the content disposition from the response header
*/
public String getContentDispositionFromResponseHeader()
{
return getLastResponse().getHeader("Content-Disposition");
}
/**
* Rebuilds {@link ServletWebRequest} used by wicket from the mock request used to build
* requests. Sometimes this method is useful when changes need to be checked without processing
* a request.
*/
public void applyRequest()
{
Request req = newServletWebRequest();
requestCycle.setRequest(req);
if (useRequestUrlAsBase)
{
requestCycle.getUrlRenderer().setBaseUrl(req.getUrl());
}
}
/**
*
* @param message
* @param condition
* @return fail with message if false
*/
private Result isTrue(String message, boolean condition)
{
if (condition)
{
return Result.pass();
}
return Result.fail(message);
}
/**
*
* @param message
* @param condition
* @return fail with message if true
*/
private Result isFalse(String message, boolean condition)
{
if (!condition)
{
return Result.pass();
}
return Result.fail(message);
}
/**
*
* @param expected
* @param actual
* @return fail with message if not equal
*/
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);
}
/**
*
* @param message
* @param object
*/
private void notNull(String message, Object object)
{
if (object == null)
{
fail(message);
}
}
/**
* Checks whether a component is visible and/or enabled before usage
*
* @param component
* @param throwException
* @return result
*/
protected Result checkUsability(final Component component, boolean throwException)
{
Result res = Result.pass();
if (component.isVisibleInHierarchy() == false)
{
res = Result.fail(
"The component is currently not visible in the hierarchy and thus you can not be used." +
" Component: " + component);
}
if (component.isEnabledInHierarchy() == false)
{
res = Result.fail(
"The component is currently not enabled in the hierarchy and thus you can not be used." +
" Component: " + component);
}
if (throwException && res.wasFailed())
{
throw new AssertionError(res.getMessage());
}
return res;
}
/**
* @return request cycle
*/
public RequestCycle getRequestCycle()
{
return requestCycle;
}
/**
* @return servlet response
*/
public MockHttpServletResponse getResponse()
{
return response;
}
/**
* @return last request
*/
public MockHttpServletRequest getLastRequest()
{
return lastRequest;
}
/**
* @return true, if exceptions are exposed
*/
public boolean isExposeExceptions()
{
return exposeExceptions;
}
/**
* @param exposeExceptions
*/
public void setExposeExceptions(boolean exposeExceptions)
{
this.exposeExceptions = exposeExceptions;
}
/**
* @return useRequestUrlAsBase
*/
public boolean isUseRequestUrlAsBase()
{
return useRequestUrlAsBase;
}
/**
* @param setBaseUrl
*/
public void setUseRequestUrlAsBase(boolean setBaseUrl)
{
useRequestUrlAsBase = setBaseUrl;
}
/**
* Starts a page, a shared resource or a {@link IRequestListener} depending on what the
* {@link IRequestMapper}s resolve for the passed url.
*
* @param _url
* the url to resolve and execute
*/
public void executeUrl(final String _url)
{
Url url = Url.parse(_url, Charset.forName(request.getCharacterEncoding()));
transform(url);
getRequest().setUrl(url);
processRequest();
}
/**
* A page that is used as the automatically created page for
* {@link BaseWicketTester#startComponentInPage(Class)} and the other variations.
* <p>
* This page caches the generated markup so that it is available even after
* {@link Component#detach()} where the {@link Component#markup component's markup cache} is
* cleared.
*/
public static class StartComponentInPage extends WebPage
{
private transient IMarkupFragment pageMarkup = null;
/**
* Construct.
*/
public StartComponentInPage()
{
setStatelessHint(false);
}
@Override
public IMarkupFragment getMarkup()
{
IMarkupFragment calculatedMarkup = null;
if (pageMarkup == null)
{
IMarkupFragment markup = super.getMarkup();
if (markup != null && markup != Markup.NO_MARKUP)
{
calculatedMarkup = markup;
pageMarkup = markup;
}
}
else
{
calculatedMarkup = pageMarkup;
}
return calculatedMarkup;
}
/**
* @param markup
*/
public void setPageMarkup(IMarkupFragment markup)
{
setMarkup(markup);
pageMarkup = markup;
}
}
private static class TestPageManagerProvider implements IPageManagerProvider
{
@Override
public IPageManager get()
{
return new MockPageManager();
}
}
private static class WicketTesterServletWebResponse extends ServletWebResponse
implements
IMetaDataBufferingWebResponse
{
private List<Cookie> cookies = new ArrayList<Cookie>();
private WicketTesterServletWebResponse(ServletWebRequest request,
MockHttpServletResponse response)
{
super(request, response);
}
@Override
public void addCookie(Cookie cookie)
{
super.addCookie(cookie);
cookies.add(cookie);
}
@Override
public void writeMetaData(WebResponse webResponse)
{
for (Cookie cookie : cookies)
{
webResponse.addCookie(cookie);
}
}
@Override
public void sendRedirect(String url)
{
super.sendRedirect(url);
try
{
getContainerResponse().sendRedirect(url);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
}
private class LastPageRecordingPageRendererProvider implements IPageRendererProvider
{
private final IPageRendererProvider delegate;
private Page lastPage;
private LastPageRecordingPageRendererProvider(IPageRendererProvider delegate)
{
this.delegate = delegate;
}
@Override
public PageRenderer apply(final RenderPageRequestHandler handler)
{
return new PageRenderer(handler)
{
@Override
public void respond(RequestCycle requestCycle)
{
delegate.apply(handler).respond(requestCycle);
// WICKET-5424 record page after wrapped renderer has responded
if (handler.getPageProvider().hasPageInstance())
{
Page renderedPage = (Page)handler.getPageProvider().getPageInstance();
if (componentInPage != null && lastPage != null && renderedPage != null &&
lastPage.getPageClass() != renderedPage.getPageClass())
{
// WICKET-3913: reset startComponent if a new page
// type is rendered
componentInPage = null;
}
lastRenderedPage = lastPage = renderedPage;
}
else
{
lastRenderedPage = null;
}
}
};
}
}
private class TestExceptionMapper implements IExceptionMapper
{
private final IExceptionMapper delegate;
private TestExceptionMapper(IExceptionMapper delegate)
{
this.delegate = delegate;
}
@Override
public IRequestHandler map(Exception e)
{
if (exposeExceptions)
{
if (e instanceof RuntimeException)
{
throw (RuntimeException)e;
}
else
{
throw new WicketRuntimeException(e);
}
}
else
{
return delegate.map(e);
}
}
}
private class TestRequestCycleProvider implements IRequestCycleProvider
{
private final IRequestCycleProvider delegate;
private TestRequestCycleProvider(IRequestCycleProvider delegate)
{
this.delegate = delegate;
}
@Override
public RequestCycle apply(RequestCycleContext context)
{
context.setRequestMapper(new TestRequestMapper(context.getRequestMapper()));
forcedHandler = null;
context.setExceptionMapper(new TestExceptionMapper(context.getExceptionMapper()));
return delegate.apply(context);
}
}
private class TestRequestMapper implements IRequestMapperDelegate
{
private final IRequestMapper delegate;
private TestRequestMapper(IRequestMapper delegate)
{
this.delegate = delegate;
}
@Override
public IRequestMapper getDelegateMapper()
{
return delegate;
}
@Override
public int getCompatibilityScore(Request request)
{
return delegate.getCompatibilityScore(request);
}
@Override
public Url mapHandler(IRequestHandler requestHandler)
{
return delegate.mapHandler(requestHandler);
}
@Override
public IRequestHandler mapRequest(Request request)
{
if (forcedHandler != null)
{
IRequestHandler handler = forcedHandler;
forcedHandler = null;
return handler;
}
else
{
return delegate.mapRequest(request);
}
}
}
public class TestFilterConfig implements FilterConfig
{
private final Map<String, String> initParameters = new HashMap<String, String>();
private TestFilterConfig()
{
initParameters.put(WicketFilter.FILTER_MAPPING_PARAM, "/servlet/*");
}
@Override
public String getFilterName()
{
return getClass().getName();
}
@Override
public ServletContext getServletContext()
{
return servletContext;
}
@Override
public String getInitParameter(String s)
{
return initParameters.get(s);
}
@Override
public Enumeration<String> getInitParameterNames()
{
throw new UnsupportedOperationException("Not implemented");
}
}
}