| /* |
| * 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("ondblclick") { |
| * 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, "ondblclick"); |
| * // 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"); |
| } |
| } |
| } |