All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.util.tester.BaseWicketTester Maven / Gradle / Ivy

Go to download

Pax Wicket Service is an OSGi extension of the Wicket framework, allowing for dynamic loading and unloading of Wicket components and pageSources.

There is a newer version: 5.0.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket.util.tester;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.wicket.Component;
import org.apache.wicket.Component.IVisitor;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.PageParameters;
import org.apache.wicket.RequestCycle;
import org.apache.wicket.Session;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.AjaxSelfUpdatingTimerBehavior;
import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.behavior.BehaviorsUtil;
import org.apache.wicket.behavior.IBehavior;
import org.apache.wicket.feedback.FeedbackMessage;
import org.apache.wicket.feedback.FeedbackMessages;
import org.apache.wicket.feedback.IFeedbackMessageFilter;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.CheckGroup;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.IFormSubmittingComponent;
import org.apache.wicket.markup.html.form.IFormVisitorParticipant;
import org.apache.wicket.markup.html.form.RadioGroup;
import org.apache.wicket.markup.html.form.SubmitLink;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.IPageLink;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.link.PageLink;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.protocol.http.HttpSessionStore;
import org.apache.wicket.protocol.http.MockHttpServletResponse;
import org.apache.wicket.protocol.http.MockWebApplication;
import org.apache.wicket.protocol.http.WebApplication;
import org.apache.wicket.protocol.http.WebRequestCycle;
import org.apache.wicket.session.ISessionStore;
import org.apache.wicket.util.diff.DiffUtil;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A helper class to ease unit testing of Wicket applications without the need for a servlet
 * container. See javadoc of WicketTester for example usage. This class can be used as
 * is, but JUnit users should use derived class WicketTester.
 * 
 * @see WicketTester
 * 
 * @author Ingram Chen
 * @author Juergen Donnerstag
 * @author Frank Bille
 * @since 1.2.6
 */
public class BaseWicketTester extends MockWebApplication
{
	/** log. */
	private static final Logger log = LoggerFactory.getLogger(BaseWicketTester.class);

	/**
	 * @author jcompagner
	 */
	private static final class TestPageSource implements ITestPageSource
	{
		private final Page page;

		private static final long serialVersionUID = 1L;

		/**
		 * Constructor.
		 * 
		 * @param page
		 */
		private TestPageSource(Page page)
		{
			this.page = page;
		}

		public Page getTestPage()
		{
			return page;
		}
	}

	/**
	 * @author frankbille
	 */
	public static class DummyWebApplication extends WebApplication
	{
		@Override
		public Class getHomePage()
		{
			return DummyHomePage.class;
		}

		@Override
		protected void outputDevelopmentModeWarning()
		{
			// Do nothing.
		}

		@Override
		protected ISessionStore newSessionStore()
		{
			// Don't use a filestore, or we spawn lots of threads, which makes
			// things slow.
			return new HttpSessionStore(this);
		}
	}

	/**
	 * Creates WicketTester and automatically create a WebApplication, but
	 * the tester will have no home page.
	 */
	public BaseWicketTester()
	{
		this(new DummyWebApplication(), null);
	}

	/**
	 * Creates WicketTester and automatically creates a WebApplication.
	 * 
	 * @param 
	 * 
	 * @param homePage
	 *            a home page Class
	 */
	public  BaseWicketTester(final Class homePage)
	{
		this(new WebApplication()
		{
			/**
			 * @see org.apache.wicket.Application#getHomePage()
			 */
			@Override
			public Class getHomePage()
			{
				return homePage;
			}

			@Override
			protected void outputDevelopmentModeWarning()
			{
				// Do nothing.
			}

			@Override
			protected ISessionStore newSessionStore()
			{
				// Don't use a filestore, or we spawn lots of threads, which
				// makes things slow.
				return new HttpSessionStore(this);
			}

		}, null);
	}

	/**
	 * Creates a WicketTester.
	 * 
	 * @param application
	 *            a WicketTester WebApplication object
	 */
	public BaseWicketTester(final WebApplication application)
	{
		this(application, null);
	}

	/**
	 * Creates a WicketTester for unit testing.
	 * 
	 * @param application
	 *            a WicketTester WebApplication object
	 * @param path
	 *            the absolute path on disk to the WebApplication's contents (e.g. war
	 *            root) - may be null
	 * 
	 * @see org.apache.wicket.protocol.http.MockWebApplication#MockWebApplication(org.apache.wicket.protocol.http.WebApplication,
	 *      String)
	 */
	public BaseWicketTester(final WebApplication application, final String path)
	{
		super(application, path);
	}

	/**
	 * Renders a Page defined in TestPageSource. This is usually used when
	 * a page does not have default constructor. For example, a ViewBook page requires
	 * a Book instance:
	 * 
	 * 
	 * tester.startPage(new TestPageSource()
	 * {
	 * 	public Page getTestPage()
	 * 	{
	 * 		Book mockBook = new Book("myBookName");
	 * 		return new ViewBook(mockBook);
	 * 	}
	 * });
	 * 
* * @param testPageSource * a Page factory that creates a test page instance * @return the rendered Page */ public final Page startPage(final ITestPageSource testPageSource) { startPage(DummyHomePage.class); DummyHomePage page = (DummyHomePage)getLastRenderedPage(); page.setTestPageSource(testPageSource); executeListener(page.getTestPageLink()); return getLastRenderedPage(); } /** * Builds and processes a request suitable for invoking a listener. The Component * must implement any of the known IListener interfaces. * * @param component * the listener to invoke */ public void executeListener(Component component) { WebRequestCycle cycle = setupRequestAndResponse(); getServletRequest().setRequestToComponent(component); processRequestCycle(cycle); } /** * Builds and processes a request suitable for executing an AbstractAjaxBehavior. * * @param behavior * an AbstractAjaxBehavior to execute */ public void executeBehavior(final AbstractAjaxBehavior behavior) { CharSequence url = behavior.getCallbackUrl(false); WebRequestCycle cycle = setupRequestAndResponse(true); getServletRequest().setRequestToRedirectString(url.toString()); processRequestCycle(cycle); } /** * Renders the Page. * * @param page * a Page to render * @return the rendered Page */ public final Page startPage(final Page page) { return startPage(new TestPageSource(page)); } /** * Renders a Page from its default constructor. * * @param * * @param pageClass * a test Page class with default constructor * @return the rendered Page */ public final Page startPage(Class pageClass) { processRequestCycle(pageClass); return getLastRenderedPage(); } /** * Renders a Page from its default constructor. * * @param * * @param pageClass * a test Page class with default constructor * @param parameters * the parameters to use for the class. * @return the rendered Page */ public final Page startPage(Class pageClass, PageParameters parameters) { processRequestCycle(pageClass, parameters); return getLastRenderedPage(); } /** * Creates a {@link FormTester} for the Form at a given path, and fills all child * {@link org.apache.wicket.markup.html.form.FormComponent}s with blank Strings. * * @param path * path to FormComponent * @return a FormTester instance for testing the Form * @see #newFormTester(String, boolean) */ public FormTester newFormTester(String path) { return newFormTester(path, true); } /** * Creates a {@link FormTester} for the Form at a given path. * * @param path * path to FormComponent * @param fillBlankString * specifies whether to fill all child FormComponent s with blank * Strings * @return a FormTester instance for testing the Form * @see FormTester */ public FormTester newFormTester(String path, boolean fillBlankString) { return new FormTester(path, (Form)getComponentFromLastRenderedPage(path), this, fillBlankString); } /** * Renders a Panel defined in TestPanelSource. The usage is similar to * {@link #startPage(ITestPageSource)}. Please note that testing Panel must use the * supplied panelId as a Component id. * *
	 * tester.startPanel(new TestPanelSource()
	 * {
	 * 	public Panel getTestPanel(String panelId)
	 * 	{
	 * 		MyData mockMyData = new MyData();
	 * 		return new MyPanel(panelId, mockMyData);
	 * 	}
	 * });
	 * 
* * @param testPanelSource * a Panel factory that creates test Panel instances * @return a rendered Panel */ public final Panel startPanel(final ITestPanelSource testPanelSource) { return (Panel)startPage(new ITestPageSource() { private static final long serialVersionUID = 1L; public Page getTestPage() { return new DummyPanelPage(testPanelSource); } }).get(DummyPanelPage.TEST_PANEL_ID); } /** * Renders a Panel from a Panel(String id) constructor. * * @param * * @param panelClass * a test Panel class with Panel(String id) constructor * @return a rendered Panel */ public final Panel startPanel(final Class panelClass) { return (Panel)startPage(new ITestPageSource() { private static final long serialVersionUID = 1L; public Page getTestPage() { return new DummyPanelPage(new ITestPanelSource() { private static final long serialVersionUID = 1L; public Panel getTestPanel(String panelId) { try { Constructor c = panelClass.getConstructor(String.class); return c.newInstance(panelId); } catch (SecurityException e) { throw convertoUnexpect(e); } catch (NoSuchMethodException e) { throw convertoUnexpect(e); } catch (InstantiationException e) { throw convertoUnexpect(e); } catch (IllegalAccessException e) { throw convertoUnexpect(e); } catch (InvocationTargetException e) { throw convertoUnexpect(e); } } }); } }).get(DummyPanelPage.TEST_PANEL_ID); } /** * A helper method for starting a component for a test without attaching it to a Page. * * Components which are somehow dependent on the page structure can not be currently tested with * this method. * * Example: * * UserDataView view = new UserDataView("view", new ListDataProvider(userList)); * tester.startComponent(view); assertEquals(4, view.size()); * * @param component */ public void startComponent(Component component) { if (component instanceof FormComponent) { ((FormComponent)component).processInput(); } component.beforeRender(); } /** * Throw "standard" WicketRuntimeException * * @param e * @return RuntimeException */ private RuntimeException convertoUnexpect(Exception e) { return new WicketRuntimeException("tester: unexpected", e); } /** * Gets the component with the given path from last rendered page. This method fails in case the * component couldn't be found, and it will return null if the component was found, but is not * visible. * * @param path * Path to component * @return The component at the path * @see org.apache.wicket.MarkupContainer#get(String) */ public Component getComponentFromLastRenderedPage(String path) { final Component component = getLastRenderedPage().get(path); if (component == null) { fail("path: '" + path + "' does not exist for page: " + Classes.simpleName(getLastRenderedPage().getClass())); return component; } if (component.isVisibleInHierarchy()) { return component; } return null; } /** * assert the text of Label component. * * @param path * path to Label component * @param expectedLabelText * expected label text * @return a Result */ public Result hasLabel(String path, String expectedLabelText) { Label label = (Label)getComponentFromLastRenderedPage(path); return isEqual(expectedLabelText, label.getDefaultModelObjectAsString()); } /** * assert PageLink link to page class. * * @param * * @param path * path to PageLink component * @param expectedPageClass * expected page class to link * @return a Result */ public Result isPageLink(String path, Class expectedPageClass) { PageLink pageLink = (PageLink)getComponentFromLastRenderedPage(path); try { for (Class type = pageLink.getClass(); type != PageLink.class.getSuperclass(); type = type.getSuperclass()) { try { Field iPageLinkField = type.getDeclaredField("pageLink"); iPageLinkField.setAccessible(true); IPageLink iPageLink = (IPageLink)iPageLinkField.get(pageLink); return isEqual(expectedPageClass, iPageLink.getPageIdentity()); } catch (NoSuchFieldException e) { continue; } } throw new WicketRuntimeException( "Is this realy a PageLink? Cannot find 'pageLink' field"); } catch (SecurityException e) { throw convertoUnexpect(e); } catch (IllegalAccessException e) { throw convertoUnexpect(e); } } /** * assert component class * * @param * * @param path * path to component * @param expectedComponentClass * expected component class * @return a Result */ public Result isComponent(String path, Class expectedComponentClass) { Component component = getComponentFromLastRenderedPage(path); if (component == null) { return Result.fail("Component not found: " + path); } return isTrue("component '" + Classes.simpleName(component.getClass()) + "' is not type:" + Classes.simpleName(expectedComponentClass), expectedComponentClass.isAssignableFrom(component.getClass())); } /** * assert component visible. * * @param path * path to component * @return a Result */ public Result isVisible(String path) { Component component = getLastRenderedPage().get(path); if (component == null) { fail("path: '" + path + "' does no exist for page: " + Classes.simpleName(getLastRenderedPage().getClass())); } return isTrue("component '" + path + "' is not visible", component.isVisibleInHierarchy()); } /** * assert component invisible. * * @param path * path to component * @return a Result */ public Result isInvisible(String path) { return isNull("component '" + path + "' is visible", getComponentFromLastRenderedPage(path)); } /** * assert component enabled. * * @param path * path to component * @return a Result */ public Result isEnabled(String path) { Component component = getLastRenderedPage().get(path); if (component == null) { fail("path: '" + path + "' does no exist for page: " + Classes.simpleName(getLastRenderedPage().getClass())); } return isTrue("component '" + path + "' is disabled", component.isEnabledInHierarchy()); } /** * assert component disabled. * * @param path * path to component * @return a Result */ public Result isDisabled(String path) { Component component = getLastRenderedPage().get(path); if (component == null) { fail("path: '" + path + "' does no exist for page: " + Classes.simpleName(getLastRenderedPage().getClass())); } return isFalse("component '" + path + "' is enabled", component.isEnabledInHierarchy()); } /** * assert component required. * * @param path * path to component * @return a Result */ public Result isRequired(String path) { Component component = getLastRenderedPage().get(path); if (component == null) { fail("path: '" + path + "' does no exist for page: " + Classes.simpleName(getLastRenderedPage().getClass())); } else if (component instanceof FormComponent == false) { fail("path: '" + path + "' is not a form component"); } return isRequired((FormComponent)component); } /** * assert component required. * * @param component * a form component * @return a Result */ public Result isRequired(FormComponent component) { return isTrue("component '" + component + "' is not required", component.isRequired()); } /** * assert the content of last rendered page contains(matches) regex pattern. * * @param pattern * reqex pattern to match * @return a Result */ public Result ifContains(String pattern) { return isTrue("pattern '" + pattern + "' not found", getServletResponse().getDocument() .matches("(?s).*" + pattern + ".*")); } /** * assert the content of last rendered page contains(matches) regex pattern. * * @param pattern * reqex pattern to match * @return a Result */ public Result ifContainsNot(String pattern) { return isFalse("pattern '" + pattern + "' found", getServletResponse().getDocument() .matches("(?s).*" + pattern + ".*")); } /** * assert the model of {@link ListView} use expectedList * * @param path * path to {@link ListView} component * @param expectedList * expected list in the model of {@link ListView} */ public void assertListView(String path, List expectedList) { ListView listView = (ListView)getComponentFromLastRenderedPage(path); WicketTesterHelper.assertEquals(expectedList, listView.getList()); } /** * Click the {@link Link} in the last rendered Page. *

* Simulate that AJAX is enabled. * * @see WicketTester#clickLink(String, boolean) * @param path * Click the Link in the last rendered Page. */ public void clickLink(String path) { clickLink(path, true); } /** * Click the {@link Link} in the last rendered Page. *

* This method also works for {@link AjaxLink}, {@link AjaxFallbackLink} and * {@link AjaxSubmitLink}. *

* On AjaxLinks and AjaxFallbackLinks the onClick method is invoked with a valid * AjaxRequestTarget. In that way you can test the flow of your application when using AJAX. *

* When clicking an AjaxSubmitLink the form, which the AjaxSubmitLink is attached to is first * submitted, and then the onSubmit method on AjaxSubmitLink is invoked. If you have changed * some values in the form during your test, these will also be submitted. This should not be * used as a replacement for the {@link FormTester} to test your forms. It should be used to * test that the code in your onSubmit method in AjaxSubmitLink actually works. *

* This method is also able to simulate that AJAX (javascript) is disabled on the client. This * is done by setting the isAjax parameter to false. If you have an AjaxFallbackLink you can * then check that it doesn't fail when invoked as a normal link. * * @param path * path to Link component * @param isAjax * Whether to simulate that AJAX (javascript) is enabled or not. If it's false then * AjaxLink and AjaxSubmitLink will fail, since it wouldn't work in real life. * AjaxFallbackLink will be invoked with null as the AjaxRequestTarget parameter. */ public void clickLink(String path, boolean isAjax) { Component linkComponent = getComponentFromLastRenderedPage(path); checkUsability(linkComponent); // if the link is an AjaxLink, we process it differently // than a normal link if (linkComponent instanceof AjaxLink) { // If it's not ajax we fail if (isAjax == false) { fail("Link " + path + "is an AjaxLink and will " + "not be invoked when AJAX (javascript) is disabled."); } AjaxLink link = (AjaxLink)linkComponent; setupRequestAndResponse(true); WebRequestCycle requestCycle = createRequestCycle(); AjaxRequestTarget target = getApplication().newAjaxRequestTarget(link.getPage()); requestCycle.setRequestTarget(target); link.onClick(target); // process the request target processRequestCycle(requestCycle); } // AjaxFallbackLinks is processed like an AjaxLink if isAjax is true // If it's not handling of the linkComponent is passed through to the // Link. else if (linkComponent instanceof AjaxFallbackLink && isAjax) { AjaxFallbackLink link = (AjaxFallbackLink)linkComponent; setupRequestAndResponse(true); WebRequestCycle requestCycle = createRequestCycle(); AjaxRequestTarget target = getApplication().newAjaxRequestTarget(link.getPage()); requestCycle.setRequestTarget(target); link.onClick(target); // process the request target processRequestCycle(requestCycle); } // if the link is an AjaxSubmitLink, we need to find the form // from it using reflection so we know what to submit. else if (linkComponent instanceof AjaxSubmitLink) { // If it's not ajax we fail if (isAjax == false) { fail("Link " + path + "is an AjaxSubmitLink and " + "will not be invoked when AJAX (javascript) is disabled."); } AjaxSubmitLink link = (AjaxSubmitLink)linkComponent; // We cycle through the attached behaviors and select the // LAST matching behavior as the one we handle. List behaviors = link.getBehaviors(); AjaxFormSubmitBehavior ajaxFormSubmitBehavior = null; for (IBehavior behavior : behaviors) { if (behavior instanceof AjaxFormSubmitBehavior) { AjaxFormSubmitBehavior submitBehavior = (AjaxFormSubmitBehavior)behavior; ajaxFormSubmitBehavior = submitBehavior; } } String failMessage = "No form submit behavior found on the submit link. Strange!!"; notNull(failMessage, ajaxFormSubmitBehavior); WebRequestCycle requestCycle = setupRequestAndResponse(true); setupAjaxSubmitRequestParameters(linkComponent, ajaxFormSubmitBehavior); // Ok, finally we "click" the link ajaxFormSubmitBehavior.onRequest(); // process the request target processRequestCycle(requestCycle); } /* * If the link is a submitlink then we pretend to have clicked it */ else if (linkComponent instanceof SubmitLink) { SubmitLink submitLink = (SubmitLink)linkComponent; String pageRelativePath = submitLink.getInputName(); getParametersForNextRequest().put(pageRelativePath, new String[] { "x" }); Form form = submitLink.getForm(); form.visitFormComponents(new FormComponent.IVisitor() { public Object formComponent(IFormVisitorParticipant formComponent) { FormComponent component = (FormComponent)formComponent; if (getParametersForNextRequest().containsKey(component.getInputName()) == false) { getParametersForNextRequest().put(component.getInputName(), new String[] { component.getDefaultModelObjectAsString() }); } return IVisitor.CONTINUE_TRAVERSAL; } }); submitForm(submitLink.getForm().getPageRelativePath()); } // if the link is a normal link (or ResourceLink) else if (linkComponent instanceof AbstractLink) { AbstractLink link = (AbstractLink)linkComponent; /* * If the link is a bookmarkable link, then we need to transfer the parameters to the * next request. */ if (link instanceof BookmarkablePageLink) { BookmarkablePageLink bookmarkablePageLink = (BookmarkablePageLink)link; try { BookmarkablePageLink.class.getDeclaredField("parameters"); Method getParametersMethod = BookmarkablePageLink.class.getDeclaredMethod( "getPageParameters", (Class[])null); getParametersMethod.setAccessible(true); PageParameters parameters = (PageParameters)getParametersMethod.invoke( bookmarkablePageLink, (Object[])null); setParametersForNextRequest(parameters.toRequestParameters()); } catch (Exception e) { fail("Internal error in WicketTester. " + "Please report this in Wickets Issue Tracker."); } } executeListener(link); } else { fail("Link " + path + " is not a Link, AjaxLink, AjaxFallbackLink or AjaxSubmitLink"); } } /** * Submits the Form in the last rendered Page. * * @param path * path to Form component */ public void submitForm(String path) { Form form = (Form)getComponentFromLastRenderedPage(path); executeListener(form); } /** * Sets a parameter for the Component with the given path to be used with the next * request. *

* NOTE: this method only works when a Page was rendered first. * * @param componentPath * path to the Component * @param value * the parameter value to set */ public void setParameterForNextRequest(String componentPath, Object value) { if (getLastRenderedPage() == null) { fail("before using this method, at least one page has to be rendered"); } Component c = getComponentFromLastRenderedPage(componentPath); if (c == null) { fail("component " + componentPath + " was not found"); return; } if (c instanceof FormComponent) { getParametersForNextRequest().put(((FormComponent)c).getInputName(), new String[] { value.toString() }); } else { getParametersForNextRequest().put(c.getPath(), new String[] { value.toString() }); } } /** * Asserts the last rendered Page class. * * FIXME explain why the code is so complicated to compare two classes, or simplify * * @param * * @param expectedRenderedPageClass * expected class of last rendered page * @return a Result */ public Result isRenderedPage(Class expectedRenderedPageClass) { Page page = getLastRenderedPage(); if (page == null) { return Result.fail("page was null"); } if (!page.getClass().isAssignableFrom(expectedRenderedPageClass)) { return isEqual(Classes.simpleName(expectedRenderedPageClass), Classes.simpleName(page.getClass())); } return Result.pass(); } /** * Asserts last rendered Page against an expected HTML document. *

* Use -Dwicket.replace.expected.results=true to automatically replace the expected * output file. *

* * @param pageClass * used to load the File (relative to clazz package) * @param filename * expected output File name * @throws Exception */ public void assertResultPage(final Class pageClass, final String filename) throws Exception { // Validate the document String document = getServletResponse().getDocument(); DiffUtil.validatePage(document, pageClass, filename, true); } /** * Asserts last rendered Page against an expected HTML document as a * String. * * @param expectedDocument * expected output * @return a Result * @throws Exception */ public Result isResultPage(final String expectedDocument) throws Exception { // Validate the document String document = getServletResponse().getDocument(); return isTrue("expected rendered page equals", document.equals(expectedDocument)); } /** * Asserts no error-level feedback messages. * * @return a Result */ public Result hasNoErrorMessage() { List messages = getMessages(FeedbackMessage.ERROR); return isTrue( "expect no error message, but contains\n" + WicketTesterHelper.asLined(messages), messages.isEmpty()); } /** * Asserts no info-level feedback messages. * * @return a Result */ public Result hasNoInfoMessage() { List messages = getMessages(FeedbackMessage.INFO); return isTrue( "expect no info message, but contains\n" + WicketTesterHelper.asLined(messages), messages.isEmpty()); } /** * Retrieves FeedbackMessages. * * @param level * level of feedback message, for example: * FeedbackMessage.DEBUG or FeedbackMessage.INFO.. etc * @return List of messages (as Strings) * @see FeedbackMessage */ public List getMessages(final int level) { FeedbackMessages feedbackMessages = Session.get().getFeedbackMessages(); List allMessages = feedbackMessages.messages(new IFeedbackMessageFilter() { private static final long serialVersionUID = 1L; public boolean accept(FeedbackMessage message) { return message.getLevel() == level; } }); List actualMessages = new ArrayList(); for (FeedbackMessage message : allMessages) { actualMessages.add(message.getMessage()); } return actualMessages; } /** * Dumps the source of last rendered Page. */ public void dumpPage() { log.info(getServletResponse().getDocument()); } /** * Dumps the Component trees. */ public void debugComponentTrees() { debugComponentTrees(""); } /** * Dumps the Component trees to log. Show only the Components whose * paths contain the filter String. * * @param filter * a filter String */ public void debugComponentTrees(String filter) { log.info("debugging ----------------------------------------------"); for (WicketTesterHelper.ComponentData obj : WicketTesterHelper.getComponentData(getLastRenderedPage())) { if (obj.path.matches(".*" + filter + ".*")) { log.info("path\t" + obj.path + " \t" + obj.type + " \t[" + obj.value + "]"); } } } /** * Tests that a Component has been added to a AjaxRequestTarget, using * {@link AjaxRequestTarget#addComponent(Component)}. This method actually tests that a * Component is on the Ajax response sent back to the client. *

* PLEASE NOTE! This method doesn't actually insert the Component in the client DOM * tree, using Javascript. But it shouldn't be needed because you have to trust that the Wicket * Ajax Javascript just works. * * @param component * the Component to test * @return a Result */ public Result isComponentOnAjaxResponse(Component component) { String failMessage = "A component which is null could not have been added to the AJAX response"; notNull(failMessage, component); Result result; // test that the component renders the placeholder tag if it's not // visible if (!component.isVisible()) { failMessage = "A component which is invisible and doesn't render a placeholder tag" + " will not be rendered at all and thus won't be accessible for subsequent AJAX interaction"; result = isTrue(failMessage, component.getOutputMarkupPlaceholderTag()); if (result.wasFailed()) { return result; } } // Get the AJAX response String ajaxResponse = getServletResponse().getDocument(); // Test that the previous response was actually a AJAX response failMessage = "The Previous response was not an AJAX response. " + "You need to execute an AJAX event, using clickLink, before using this assert"; boolean isAjaxResponse = Pattern.compile( "^<\\?xml version=\"1.0\" encoding=\".*?\"\\?>") .matcher(ajaxResponse) .find(); result = isTrue(failMessage, isAjaxResponse); if (result.wasFailed()) { return result; } // See if the component has a markup id String markupId = component.getMarkupId(); failMessage = "The component doesn't have a markup id, " + "which means that it can't have been added to the AJAX response"; result = isTrue(failMessage, !Strings.isEmpty(markupId)); if (result.wasFailed()) { return result; } // Look for that the component is on the response, using the markup id boolean isComponentInAjaxResponse = ajaxResponse.matches("(?s).*]*?>.*"); failMessage = "Component wasn't found in the AJAX response"; return isTrue(failMessage, isComponentInAjaxResponse); } /** * Simulates the firing of an Ajax event. * * @see #executeAjaxEvent(Component, String) * * @since 1.2.3 * @param componentPath * the Component path * @param event * the event which we simulate being fired. If event is * null, the test will fail. */ public void executeAjaxEvent(String componentPath, String event) { Component component = getComponentFromLastRenderedPage(componentPath); executeAjaxEvent(component, event); } /** * Simulates the firing of all ajax timer behaviors on the page * * @param page * the page which timers will be executed */ public void executeAllTimerBehaviors(final MarkupContainer page) { // execute all timer behaviors for the page itself internalExecuteAllTimerBehaviors(page); // and for all its children page.visitChildren(Component.class, new IVisitor() { public Object component(final Component component) { internalExecuteAllTimerBehaviors(component); return CONTINUE_TRAVERSAL; } }); } private void internalExecuteAllTimerBehaviors(final Component component) { List behaviors = BehaviorsUtil.getBehaviors(component, AbstractAjaxTimerBehavior.class); for (IBehavior b : behaviors) { AbstractAjaxTimerBehavior timer = (AbstractAjaxTimerBehavior) b; if (!timer.isStopped()) { if (log.isDebugEnabled()) { log.debug("Triggering AjaxSelfUpdatingTimerBehavior: {}", component.getClassRelativePath()); } checkUsability(component); executeBehavior(timer); } } } /** * Simulates the firing of an Ajax event. You add an Ajax event to a Component by * using: * *

	 *     ...
	 *     component.add(new AjaxEventBehavior("ondblclick") {
	 *         public void onEvent(AjaxRequestTarget) {}
	 *     });
	 *     ...
	 * 
* * You can then test that the code inside onEvent actually does what it's supposed * to, using the WicketTester: * *
	 *     ...
	 *     tester.executeAjaxEvent(component, "ondblclick");
	 *     // Test that the code inside onEvent is correct.
	 *     ...
	 * 
* * This also works with AjaxFormSubmitBehavior, where it will "submit" the * Form before executing the command. *

* PLEASE NOTE! This method doesn't actually insert the Component in the client DOM * tree, using Javascript. * * * @param component * the Component that has the AjaxEventBehavior we want to * test. If the Component is null, the test will fail. * @param event * the event to simulate being fired. If event is null, the * test will fail. */ public void executeAjaxEvent(final Component component, final String event) { setCreateAjaxRequest(true); String failMessage = "Can't execute event on a component which is null."; notNull(failMessage, component); failMessage = "event must not be null"; notNull(failMessage, event); checkUsability(component); // Run through all the behavior and select the LAST ADDED behavior which // matches the event parameter. AjaxEventBehavior ajaxEventBehavior = null; List behaviors = component.getBehaviors(); for (IBehavior behavior : behaviors) { // AjaxEventBehavior is the one to look for if (behavior instanceof AjaxEventBehavior) { AjaxEventBehavior tmp = (AjaxEventBehavior)behavior; if (event.equals(tmp.getEvent())) { ajaxEventBehavior = tmp; } } } // If there haven't been found any event behaviors on the component // which matches the parameters we fail. failMessage = "No AjaxEventBehavior found on component: " + component.getId() + " which matches the event: " + event; notNull(failMessage, ajaxEventBehavior); // when the requestcycle is not created via // setupRequestAndResponse(true), than create a new // one WebRequestCycle requestCycle = resolveRequestCycle(); if (!requestCycle.getWebRequest().isAjax()) { throw new IllegalStateException( "The ServletWebRequest was created without wicket-ajax header. Please use tester.setCreateAjaxRequest(true)"); } // If the event is an FormSubmitBehavior then also "submit" the form if (ajaxEventBehavior instanceof AjaxFormSubmitBehavior) { AjaxFormSubmitBehavior ajaxFormSubmitBehavior = (AjaxFormSubmitBehavior)ajaxEventBehavior; setupAjaxSubmitRequestParameters(component, ajaxFormSubmitBehavior); } // process the event ajaxEventBehavior.onRequest(); // process the request target processRequestCycle(requestCycle); } /** * * @return WebRequestCycle */ protected WebRequestCycle resolveRequestCycle() { // initialize the request only if needed to allow the user to pass // request parameters, see WICKET-254 WebRequestCycle requestCycle; if (RequestCycle.get() == null) { requestCycle = setupRequestAndResponse(); } else { requestCycle = (WebRequestCycle)RequestCycle.get(); // If a ajax request is requested but the existing is not, than we // still need to create // a new one if ((requestCycle.getWebRequest().isAjax() == false) && (isCreateAjaxRequest() == true)) { setParametersForNextRequest(requestCycle.getWebRequest().getParameterMap()); requestCycle = setupRequestAndResponse(); } } return requestCycle; } /** * Retrieves a TagTester based on a wicket:id. If more * Components exist with the same wicket:id in the markup, only the * first one is returned. * * @param wicketId * the wicket:id to search for * @return the TagTester for the tag which has the given wicket:id */ public TagTester getTagByWicketId(String wicketId) { return TagTester.createTagByAttribute(getServletResponse().getDocument(), "wicket:id", wicketId); } /** * Modified version of BaseWicketTester#getTagByWicketId(String) that returns all matching tags * instead of just the first. * * @see BaseWicketTester#getTagByWicketId(String) */ public static List getTagsByWicketId(WicketTester tester, String wicketId) { return TagTester.createTagsByAttribute(tester.getServletResponse().getDocument(), "wicket:id", wicketId, false); } /** * Retrieves a TagTester based on an DOM id. If more Components exist * with the same id in the markup, only the first one is returned. * * @param id * the DOM id to search for. * @return the TagTester for the tag which has the given DOM id */ public TagTester getTagById(String id) { return TagTester.createTagByAttribute(getServletResponse().getDocument(), "id", id); } /** * Helper method for all the places where an Ajax call should submit an associated * Form. * * @param component * The component the behavior is attached to * @param behavior * The AjaxFormSubmitBehavior with the Form to "submit" */ private void setupAjaxSubmitRequestParameters(final Component component, AjaxFormSubmitBehavior behavior) { // The form that needs to be "submitted". Form form = behavior.getForm(); String failMessage = "No form attached to the submitlink."; notNull(failMessage, form); checkUsability(form); final Map requestParameters = getServletRequest().getParameterMap(); /* * Means that an button or an ajax link was clicked and needs to be added to the request * parameters to their form component correctly resolves the submit origin */ if (component instanceof Button) { Button clickedButton = (Button)component; getServletRequest().setParameter(clickedButton.getInputName(), clickedButton.getValue()); } else if (component instanceof AjaxSubmitLink) { String inputName = ((IFormSubmittingComponent)component).getInputName(); requestParameters.put(inputName, new String[] { "x" }); } form.visitFormComponents(new FormComponent.AbstractVisitor() { @Override public void onFormComponent(FormComponent formComponent) { /* * It is important to don't add every button input name as an request parameter to * respect the submit origin */ if (!(formComponent instanceof RadioGroup) && !(formComponent instanceof CheckGroup) && !(formComponent instanceof Button) && formComponent.isVisibleInHierarchy() && formComponent.isEnabledInHierarchy()) { if (!((formComponent instanceof IFormSubmittingComponent) && (component instanceof IFormSubmittingComponent)) || (component == formComponent)) { String name = formComponent.getInputName(); String value = formComponent.getValue(); // Set request parameter with the field value, but do not modify an existing // request parameter explicitly set using FormTester.setValue() if (!getServletRequest().getParameterMap().containsKey(name) && !getParametersForNextRequest().containsKey(name)) { getServletRequest().setParameter(name, value); getParametersForNextRequest().put(name, new String[] { value }); } } } } }); } /** * Retrieves the content type from the response header. * * @return the content type from the response header */ public String getContentTypeFromResponseHeader() { String contentType = ((MockHttpServletResponse)getWicketResponse().getHttpServletResponse()).getHeader("Content-Type"); if (contentType == null) { throw new WicketRuntimeException("No Content-Type header found"); } return contentType; } /** * Retrieves the content length from the response header. * * @return the content length from the response header */ public int getContentLengthFromResponseHeader() { String contentLength = ((MockHttpServletResponse)getWicketResponse().getHttpServletResponse()).getHeader("Content-Length"); if (contentLength == null) { throw new WicketRuntimeException("No Content-Length header found"); } return Integer.parseInt(contentLength); } /** * Retrieves the last-modified value from the response header. * * @return the last-modified value from the response header */ public String getLastModifiedFromResponseHeader() { return ((MockHttpServletResponse)getWicketResponse().getHttpServletResponse()).getHeader("Last-Modified"); } /** * Retrieves the content disposition from the response header. * * @return the content disposition from the response header */ public String getContentDispositionFromResponseHeader() { return ((MockHttpServletResponse)getWicketResponse().getHttpServletResponse()).getHeader("Content-Disposition"); } private Result isTrue(String message, boolean condition) { if (condition) { return Result.pass(); } return Result.fail(message); } private Result isFalse(String message, boolean condition) { if (!condition) { return Result.pass(); } return Result.fail(message); } protected final Result isEqual(Object expected, Object actual) { if (expected == null && actual == null) { return Result.pass(); } if (expected != null && expected.equals(actual)) { return Result.pass(); } String message = "expected:<" + expected + "> but was:<" + actual + ">"; return Result.fail(message); } private void notNull(String message, Object object) { if (object == null) { fail(message); } } private Result isNull(String message, Object object) { if (object != null) { return Result.fail(message); } return Result.pass(); } /** * Checks whether a component is visible and/or enabled before usage * * @param component */ protected void checkUsability(final Component component) { if (component.isVisibleInHierarchy() == false) { fail("The component is currently not visible in the hierarchy and thus you can not be used." + " Component: " + component); } if (component.isEnabledInHierarchy() == false) { fail("The component is currently not enabled in the hierarchy and thus you can not be used." + " Component: " + component); } } protected final void fail(String message) { throw new WicketRuntimeException(message); } /** * @param rc */ // FIXME 1.5: REMOVE THIS HACK. Currently there is no way to call // requestcycle.onbeginrequest() from outside and since tester shortcircuits // the normal // workflow it is necessary to call onbeginrequest manually @Deprecated public static void callOnBeginRequest(RequestCycle rc) { try { Method method = RequestCycle.class.getDeclaredMethod("onBeginRequest", (Class[])null); method.setAccessible(true); method.invoke(rc, (Object[])null); } catch (Exception e) { throw new RuntimeException("Exception invoking requestcycle.onbeginrequest()", e); } } }