0001: /*
0002: * $Id: WicketTester.java 471275 2006-11-04 22:11:25Z frankbille $
0003: * $Revision: 471275 $
0004: * $Date: 2006-11-04 23:11:25 +0100 (Sat, 04 Nov 2006) $
0005: *
0006: * ==============================================================================
0007: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0008: * use this file except in compliance with the License. You may obtain a copy of
0009: * the License at
0010: *
0011: * http://www.apache.org/licenses/LICENSE-2.0
0012: *
0013: * Unless required by applicable law or agreed to in writing, software
0014: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0015: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0016: * License for the specific language governing permissions and limitations under
0017: * the License.
0018: */
0019: package wicket.util.tester;
0020:
0021: import java.lang.reflect.Constructor;
0022: import java.lang.reflect.Field;
0023: import java.lang.reflect.InvocationTargetException;
0024: import java.util.ArrayList;
0025: import java.util.Arrays;
0026: import java.util.Iterator;
0027: import java.util.List;
0028:
0029: import junit.framework.Assert;
0030:
0031: import org.apache.commons.logging.Log;
0032: import org.apache.commons.logging.LogFactory;
0033:
0034: import wicket.Component;
0035: import wicket.Page;
0036: import wicket.PageParameters;
0037: import wicket.RequestCycle;
0038: import wicket.WicketRuntimeException;
0039: import wicket.ajax.AjaxEventBehavior;
0040: import wicket.ajax.AjaxRequestTarget;
0041: import wicket.ajax.form.AjaxFormSubmitBehavior;
0042: import wicket.ajax.markup.html.AjaxFallbackLink;
0043: import wicket.ajax.markup.html.AjaxLink;
0044: import wicket.ajax.markup.html.form.AjaxSubmitLink;
0045: import wicket.behavior.IBehavior;
0046: import wicket.feedback.FeedbackMessage;
0047: import wicket.feedback.FeedbackMessages;
0048: import wicket.feedback.IFeedbackMessageFilter;
0049: import wicket.markup.html.basic.Label;
0050: import wicket.markup.html.form.Button;
0051: import wicket.markup.html.form.CheckGroup;
0052: import wicket.markup.html.form.Form;
0053: import wicket.markup.html.form.FormComponent;
0054: import wicket.markup.html.form.RadioGroup;
0055: import wicket.markup.html.link.BookmarkablePageLink;
0056: import wicket.markup.html.link.IPageLink;
0057: import wicket.markup.html.link.Link;
0058: import wicket.markup.html.link.PageLink;
0059: import wicket.markup.html.list.ListView;
0060: import wicket.markup.html.panel.Panel;
0061: import wicket.protocol.http.MockWebApplication;
0062: import wicket.util.lang.Classes;
0063: import wicket.util.string.Strings;
0064:
0065: /**
0066: * A helper to ease unit testing of Wicket applications without the need for a
0067: * servlet container. To start a test, we can use either startPage() or
0068: * startPanel():
0069: *
0070: * <pre>
0071: * // production page
0072: * public class MyPage extends WebPage
0073: * {
0074: * public MyPage()
0075: * {
0076: * add(new Label("myMessage", "Hello!"));
0077: * add(new Link("toYourPage")
0078: * {
0079: * public void onClick()
0080: * {
0081: * setResponsePage(new YourPage("Hi!"));
0082: * }
0083: * });
0084: * }
0085: * }
0086: * </pre>
0087: *
0088: * <pre>
0089: * // test code
0090: * private WicketTester tester;
0091: *
0092: * public void setUp()
0093: * {
0094: * tester = new WicketTester();
0095: * }
0096: *
0097: * public void testRenderMyPage()
0098: * {
0099: * //start and render the test page
0100: * tester.startPage(MyPage.class);
0101: *
0102: * //assert rendered page class
0103: * tester.assertRenderedPage(MyPage.class);
0104: *
0105: * //assert rendered label component
0106: * tester.assertLabel("myMessage", "Hello!");
0107: * }
0108: * </pre>
0109: *
0110: * Above example is straight forward: start MyPage.class and assert Label it
0111: * rendered. Next, we try to navigate through link:
0112: *
0113: * <pre>
0114: * // production page
0115: * public class YourPage extends WebPage
0116: * {
0117: * public YourPage(String message)
0118: * {
0119: * add(new Label("yourMessage", message));
0120: * info("Wicket Rocks ;-)");
0121: * }
0122: * }
0123: *
0124: * //test code
0125: * public void testLinkToYourPage()
0126: * {
0127: * tester.startPage(MyPage.class);
0128: *
0129: * //click link and render
0130: * tester.clickLink("toYourPage");
0131: *
0132: * tester.assertRenderedPage(YourPage.class);
0133: * tester.assertLabel("yourMessage", "Hi!");
0134: * }
0135: * </pre>
0136: *
0137: * <code>tester.clickLink(path);</code> will simulate user click on the
0138: * component (in this case, it's a <code>Link</code>) and render the response
0139: * page <code>YourPage</code>. Ok, unit test of <code>MyPage</code> is
0140: * completed. Now we test <code>YourPage</code> standalone:
0141: *
0142: * <pre>
0143: * //test code
0144: * public void testRenderYourPage()
0145: * {
0146: * // provide page instance source for WicketTester
0147: * tester.startPage(new TestPageSource()
0148: * {
0149: * public Page getTestPage()
0150: * {
0151: * return new YourPage("mock message");
0152: * }
0153: * });
0154: *
0155: * tester.assertRenderedPage(YourPage.class);
0156: * tester.assertLabel("yourMessage", "mock message");
0157: *
0158: * // assert feedback messages in INFO Level
0159: * tester.assertInfoMessages(new String[] { "Wicket Rocks ;-)" });
0160: *
0161: * }
0162: * </pre>
0163: *
0164: * Instead of <code>tester.startPage(pageClass)</code>, we define a
0165: * {@link wicket.util.tester.ITestPageSource} to provide testing page instance
0166: * for WicketTester. This is necessary because <code>YourPage</code> uses a
0167: * custom constructor, which is very common for transfering model data, can not
0168: * be instansiated by reflection. Finally, we use
0169: * <code>assertInfoMessages</code> to assert there is a feedback message
0170: * "Wicket Rocks ;-)" in INFO level.
0171: *
0172: * TODO General: Example usage of FormTester
0173: *
0174: * @author Ingram Chen
0175: * @author Juergen Donnerstag
0176: * @author Frank Bille
0177: */
0178: public class WicketTester extends MockWebApplication {
0179: /** log. */
0180: private static final Log log = LogFactory
0181: .getLog(WicketTester.class);
0182:
0183: /**
0184: * create WicketTester with null path
0185: *
0186: * @see #WicketTester(String)
0187: */
0188: public WicketTester() {
0189: this (null);
0190: }
0191:
0192: /**
0193: * create a WicketTester to help unit testing.
0194: *
0195: * @param path
0196: * The absolute path on disk to the web application contents
0197: * (e.g. war root) - may be null
0198: *
0199: * @see wicket.protocol.http.MockWebApplication#MockWebApplication(String)
0200: */
0201: public WicketTester(final String path) {
0202: super (path);
0203: }
0204:
0205: /**
0206: * Render a page defined in <code>TestPageSource</code>. This usually
0207: * used when a page does not have default consturctor. For example, a
0208: * <code>ViewBook</code> page requires a <code>Book</code> instance:
0209: *
0210: * <pre>
0211: * tester.startPage(new TestPageSource()
0212: * {
0213: * public Page getTestPage()
0214: * {
0215: * Book mockBook = new Book("myBookName");
0216: * return new ViewBook(mockBook);
0217: * }
0218: * });
0219: * </pre>
0220: *
0221: * @param testPageSource
0222: * a page factory that creating test page instance
0223: * @return Page rendered page
0224: */
0225: public final Page startPage(ITestPageSource testPageSource) {
0226: setHomePage(DummyHomePage.class);
0227: setupRequestAndResponse();
0228: processRequestCycle();
0229: DummyHomePage page = (DummyHomePage) getLastRenderedPage();
0230: page.setTestPageSource(testPageSource);
0231:
0232: newRequestToComponent(page.getTestPageLink());
0233: return getLastRenderedPage();
0234: }
0235:
0236: /**
0237: *
0238: * @param component
0239: */
0240: private void newRequestToComponent(Component component) {
0241: setupRequestAndResponse();
0242: getServletRequest().setRequestToComponent(component);
0243: // getServletRequest().getSession().getPageMap(null);
0244: processRequestCycle();
0245: }
0246:
0247: /**
0248: * Render the page
0249: *
0250: * @param page
0251: * @return The page rendered
0252: */
0253: public final Page startPage(final Page page) {
0254: setHomePage(DummyHomePage.class);
0255: processRequestCycle(page);
0256:
0257: Page last = getLastRenderedPage();
0258:
0259: getWicketSession().touch(page);
0260: if (page != last) {
0261: getWicketSession().touch(last);
0262: }
0263: return last;
0264: }
0265:
0266: /**
0267: * Render a page from its default constructor.
0268: *
0269: * @param pageClass
0270: * a test page class with default constructor
0271: * @return Page Rendered Page
0272: */
0273: public final Page startPage(Class pageClass) {
0274: setHomePage(pageClass);
0275: setupRequestAndResponse();
0276: processRequestCycle();
0277: return getLastRenderedPage();
0278: }
0279:
0280: /**
0281: * Render a panel defined in <code>TestPanelSource</code>. The usage is
0282: * similar with {@link #startPage(ITestPageSource)}. Please note that
0283: * testing panel must use supplied <code>panelId<code> as component id.
0284: *
0285: * <pre>
0286: * tester.startPanel(new TestPanelSource()
0287: * {
0288: * public Panel getTestPanel(String panelId)
0289: * {
0290: * MyData mockMyData = new MyData();
0291: * return new MyPanel(panelId, mockMyData);
0292: * }
0293: * });
0294: * </pre>
0295: *
0296: * @param testPanelSource
0297: * a panel factory that creating test panel instance
0298: * @return Panel rendered panel
0299: */
0300: public final Panel startPanel(final TestPanelSource testPanelSource) {
0301: return (Panel) startPage(new ITestPageSource() {
0302: private static final long serialVersionUID = 1L;
0303:
0304: public Page getTestPage() {
0305: return new DummyPanelPage(testPanelSource);
0306: }
0307: }).get(DummyPanelPage.TEST_PANEL_ID);
0308: }
0309:
0310: /**
0311: * Render a panel from <code>Panel(String id)</code> constructor.
0312: *
0313: * @param panelClass
0314: * a test panel class with <code>Panel(String id)</code>
0315: * constructor
0316: * @return Panel rendered panel
0317: */
0318: public final Panel startPanel(final Class panelClass) {
0319: return (Panel) startPage(new ITestPageSource() {
0320: private static final long serialVersionUID = 1L;
0321:
0322: public Page getTestPage() {
0323: return new DummyPanelPage(new TestPanelSource() {
0324: private static final long serialVersionUID = 1L;
0325:
0326: public Panel getTestPanel(String panelId) {
0327: try {
0328: Constructor c = panelClass
0329: .getConstructor(new Class[] { String.class });
0330: return (Panel) c
0331: .newInstance(new Object[] { panelId });
0332: } catch (SecurityException e) {
0333: throw convertoUnexpect(e);
0334: } catch (NoSuchMethodException e) {
0335: throw convertoUnexpect(e);
0336: } catch (InstantiationException e) {
0337: throw convertoUnexpect(e);
0338: } catch (IllegalAccessException e) {
0339: throw convertoUnexpect(e);
0340: } catch (InvocationTargetException e) {
0341: throw convertoUnexpect(e);
0342: }
0343: }
0344: });
0345: }
0346: }).get(DummyPanelPage.TEST_PANEL_ID);
0347: }
0348:
0349: /**
0350: * Throw "standard" WicketRuntimeException
0351: *
0352: * @param e
0353: * @return RuntimeException
0354: */
0355: private RuntimeException convertoUnexpect(Exception e) {
0356: return new WicketRuntimeException("tester: unexpected", e);
0357: }
0358:
0359: /**
0360: * Gets the component with the given path from last rendered page. This
0361: * method fails in case the component couldn't be found, and it will return
0362: * null if the component was found, but is not visible.
0363: *
0364: * @param path
0365: * Path to component
0366: * @return The component at the path
0367: * @see wicket.MarkupContainer#get(String)
0368: */
0369: public Component getComponentFromLastRenderedPage(String path) {
0370: final Component component = getLastRenderedPage().get(path);
0371: if (component == null) {
0372: Assert.fail("path: '"
0373: + path
0374: + "' does no exist for page: "
0375: + Classes.simpleName(getLastRenderedPage()
0376: .getClass()));
0377: return component;
0378: }
0379: if (component.isVisibleInHierarchy()) {
0380: return component;
0381: }
0382: return null;
0383: }
0384:
0385: /**
0386: * assert the text of <code>Label</code> component.
0387: *
0388: * @param path
0389: * path to <code>Label</code> component
0390: * @param expectedLabelText
0391: * expected label text
0392: */
0393: public void assertLabel(String path, String expectedLabelText) {
0394: Label label = (Label) getComponentFromLastRenderedPage(path);
0395: Assert.assertEquals(expectedLabelText, label
0396: .getModelObjectAsString());
0397: }
0398:
0399: /**
0400: * assert <code>PageLink</code> link to page class.
0401: *
0402: * @param path
0403: * path to <code>PageLink</code> component
0404: * @param expectedPageClass
0405: * expected page class to link
0406: */
0407: public void assertPageLink(String path, Class expectedPageClass) {
0408: PageLink pageLink = (PageLink) getComponentFromLastRenderedPage(path);
0409: try {
0410: Field iPageLinkField = pageLink.getClass()
0411: .getDeclaredField("pageLink");
0412: iPageLinkField.setAccessible(true);
0413: IPageLink iPageLink = (IPageLink) iPageLinkField
0414: .get(pageLink);
0415: Assert.assertEquals(expectedPageClass, iPageLink
0416: .getPageIdentity());
0417: } catch (SecurityException e) {
0418: throw convertoUnexpect(e);
0419: } catch (NoSuchFieldException e) {
0420: throw convertoUnexpect(e);
0421: } catch (IllegalAccessException e) {
0422: throw convertoUnexpect(e);
0423: }
0424: }
0425:
0426: /**
0427: * assert component class
0428: *
0429: * @param path
0430: * path to component
0431: * @param expectedComponentClass
0432: * expected component class
0433: */
0434: public void assertComponent(String path,
0435: Class expectedComponentClass) {
0436: Component component = getComponentFromLastRenderedPage(path);
0437: Assert.assertTrue("component '"
0438: + Classes.simpleName(component.getClass())
0439: + "' is not type:"
0440: + Classes.simpleName(expectedComponentClass),
0441: expectedComponentClass.isAssignableFrom(component
0442: .getClass()));
0443: }
0444:
0445: /**
0446: * assert component visible.
0447: *
0448: * @param path
0449: * path to component
0450: */
0451: public void assertVisible(String path) {
0452: Component component = getLastRenderedPage().get(path);
0453: if (component == null) {
0454: Assert.fail("path: '"
0455: + path
0456: + "' does no exist for page: "
0457: + Classes.simpleName(getLastRenderedPage()
0458: .getClass()));
0459: }
0460:
0461: Assert.assertTrue("component '" + path + "' is not visible",
0462: component.isVisible());
0463: }
0464:
0465: /**
0466: * assert component invisible.
0467: *
0468: * @param path
0469: * path to component
0470: */
0471: public void assertInvisible(String path) {
0472: Assert.assertNull("component '" + path + "' is visible",
0473: getComponentFromLastRenderedPage(path));
0474: }
0475:
0476: /**
0477: * assert the content of last rendered page contains(matches) regex pattern.
0478: *
0479: * @param pattern
0480: * reqex pattern to match
0481: */
0482: public void assertContains(String pattern) {
0483: Assert.assertTrue("pattern '" + pattern + "' not found",
0484: getServletResponse().getDocument().matches(
0485: "(?s).*" + pattern + ".*"));
0486: }
0487:
0488: /**
0489: * assert the model of {@link ListView} use expectedList
0490: *
0491: * @param path
0492: * path to {@link ListView} component
0493: * @param expectedList
0494: * expected list in the model of {@link ListView}
0495: */
0496: public void assertListView(String path, List expectedList) {
0497: ListView listView = (ListView) getComponentFromLastRenderedPage(path);
0498: WicketTesterHelper.assertEquals(expectedList, listView
0499: .getList());
0500: }
0501:
0502: /**
0503: * create a {@link FormTester} for the form at path, and fill all child
0504: * {@link wicket.markup.html.form.FormComponent}s with blank String
0505: * initially.
0506: *
0507: * @param path
0508: * path to {@link Form} component
0509: * @return FormTester A FormTester instance for testing form
0510: * @see #newFormTester(String, boolean)
0511: */
0512: public FormTester newFormTester(String path) {
0513: return newFormTester(path, true);
0514: }
0515:
0516: /**
0517: * create a {@link FormTester} for the form at path.
0518: *
0519: * @param path
0520: * path to {@link Form} component
0521: * @param fillBlankString
0522: * specify whether fill all child
0523: * {@link wicket.markup.html.form.FormComponent}s with
0524: * blankString initially.
0525: * @return FormTester A FormTester instance for testing form
0526: * @see FormTester
0527: */
0528: public FormTester newFormTester(String path, boolean fillBlankString) {
0529: return new FormTester(path,
0530: (Form) getComponentFromLastRenderedPage(path), this ,
0531: fillBlankString);
0532: }
0533:
0534: /**
0535: * Click the {@link Link} in the last rendered Page.
0536: * <p>
0537: * Simulate that AJAX is enabled.
0538: *
0539: * @see WicketTester#clickLink(String, boolean)
0540: * @param path
0541: * Click the <code>Link</code> in the last rendered Page.
0542: */
0543: public void clickLink(String path) {
0544: clickLink(path, true);
0545: }
0546:
0547: /**
0548: * Click the {@link Link} in the last rendered Page.
0549: * <p>
0550: * This method also works for {@link AjaxLink}, {@link AjaxFallbackLink}
0551: * and {@link AjaxSubmitLink}.
0552: * <p>
0553: * On AjaxLinks and AjaxFallbackLinks the onClick method is invoked with a
0554: * valid AjaxRequestTarget. In that way you can test the flow of your
0555: * application when using AJAX.
0556: * <p>
0557: * When clicking an AjaxSubmitLink the form, which the AjaxSubmitLink is
0558: * attached to is first submitted, and then the onSubmit method on
0559: * AjaxSubmitLink is invoked. If you have changed some values in the form
0560: * during your test, these will also be submitted. This should not be used
0561: * as a replacement for the {@link FormTester} to test your forms. It should
0562: * be used to test that the code in your onSubmit method in AjaxSubmitLink
0563: * actually works.
0564: * <p>
0565: * This method is also able to simulate that AJAX (javascript) is disabled
0566: * on the client. This is done by setting the isAjax parameter to false. If
0567: * you have an AjaxFallbackLink you can then check that it doesn't fail when
0568: * invoked as a normal link.
0569: *
0570: * @param path
0571: * path to <code>Link</code> component
0572: * @param isAjax
0573: * Whether to simulate that AJAX (javascript) is enabled or not.
0574: * If it's false then AjaxLink and AjaxSubmitLink will fail,
0575: * since it wouldn't work in real life. AjaxFallbackLink will be
0576: * invoked with null as the AjaxRequestTarget parameter.
0577: */
0578: public void clickLink(String path, boolean isAjax) {
0579: Component linkComponent = getComponentFromLastRenderedPage(path);
0580:
0581: // if the link is an AjaxLink, we process it differently
0582: // than a normal link
0583: if (linkComponent instanceof AjaxLink) {
0584: // If it's not ajax we fail
0585: if (isAjax == false) {
0586: Assert
0587: .fail("Link "
0588: + path
0589: + "is an AjaxLink and will "
0590: + "not be invoked when AJAX (javascript) is disabled.");
0591: }
0592:
0593: AjaxLink link = (AjaxLink) linkComponent;
0594:
0595: setupRequestAndResponse();
0596: RequestCycle requestCycle = createRequestCycle();
0597: AjaxRequestTarget target = new AjaxRequestTarget();
0598: requestCycle.setRequestTarget(target);
0599:
0600: link.onClick(target);
0601:
0602: // process the request target
0603: target.respond(requestCycle);
0604: }
0605: // AjaxFallbackLinks is processed like an AjaxLink if isAjax is true
0606: // If it's not handling of the linkComponent is passed through to the
0607: // Link.
0608: else if (linkComponent instanceof AjaxFallbackLink && isAjax) {
0609: AjaxFallbackLink link = (AjaxFallbackLink) linkComponent;
0610:
0611: setupRequestAndResponse();
0612: RequestCycle requestCycle = createRequestCycle();
0613: AjaxRequestTarget target = new AjaxRequestTarget();
0614: requestCycle.setRequestTarget(target);
0615:
0616: link.onClick(target);
0617:
0618: // process the request target
0619: target.respond(requestCycle);
0620: }
0621: // if the link is an AjaxSubmitLink, we need to find the form
0622: // from it using reflection so we know what to submit.
0623: else if (linkComponent instanceof AjaxSubmitLink) {
0624: // If it's not ajax we fail
0625: if (isAjax == false) {
0626: Assert
0627: .fail("Link "
0628: + path
0629: + "is an AjaxSubmitLink and "
0630: + "will not be invoked when AJAX (javascript) is disabled.");
0631: }
0632:
0633: AjaxSubmitLink link = (AjaxSubmitLink) linkComponent;
0634:
0635: // We cycle through the attached behaviors and select the
0636: // LAST matching behavior as the one we handle.
0637: List behaviors = link.getBehaviors();
0638: AjaxFormSubmitBehavior ajaxFormSubmitBehavior = null;
0639: for (Iterator iter = behaviors.iterator(); iter.hasNext();) {
0640: Object behavior = iter.next();
0641:
0642: if (behavior instanceof AjaxFormSubmitBehavior) {
0643: AjaxFormSubmitBehavior submitBehavior = (AjaxFormSubmitBehavior) behavior;
0644: ajaxFormSubmitBehavior = submitBehavior;
0645: }
0646: }
0647:
0648: String failMessage = "No form submit behavior found on the submit link. Strange!!";
0649: Assert.assertNotNull(failMessage, ajaxFormSubmitBehavior);
0650:
0651: setupRequestAndResponse();
0652: RequestCycle requestCycle = createRequestCycle();
0653:
0654: submitAjaxFormSubmitBehavior(ajaxFormSubmitBehavior);
0655:
0656: // Ok, finally we "click" the link
0657: ajaxFormSubmitBehavior.onRequest();
0658:
0659: // process the request target
0660: requestCycle.getRequestTarget().respond(requestCycle);
0661: }
0662: // if the link is a normal link
0663: else if (linkComponent instanceof Link) {
0664: Link link = (Link) linkComponent;
0665:
0666: /*
0667: * If the link is a bookmarkable link, then we need to transfer the
0668: * parameters to the next request.
0669: */
0670: if (link instanceof BookmarkablePageLink) {
0671: BookmarkablePageLink bookmarkablePageLink = (BookmarkablePageLink) link;
0672: try {
0673: Field parametersField = BookmarkablePageLink.class
0674: .getDeclaredField("parameters");
0675: parametersField.setAccessible(true);
0676: PageParameters parameters = (PageParameters) parametersField
0677: .get(bookmarkablePageLink);
0678: setParametersForNextRequest(parameters);
0679: } catch (Exception e) {
0680: Assert
0681: .fail("Internal error in WicketTester. "
0682: + "Please report this in Wickets Issue Tracker.");
0683: }
0684:
0685: }
0686:
0687: newRequestToComponent(link);
0688: } else {
0689: Assert
0690: .fail("Link "
0691: + path
0692: + " is not a Link, AjaxLink, AjaxFallbackLink or AjaxSubmitLink");
0693: }
0694: }
0695:
0696: /**
0697: * submit the <code>Form</code> in the last rendered Page.
0698: *
0699: * @param path
0700: * path to <code>Form</code> component
0701: */
0702: public void submitForm(String path) {
0703: Form form = (Form) getComponentFromLastRenderedPage(path);
0704: newRequestToComponent(form);
0705: }
0706:
0707: /**
0708: * Sets a parameter for the component with the given path to be used with
0709: * the next request. NOTE: this method only works when a page was rendered
0710: * first.
0711: *
0712: * @param componentPath
0713: * path of the component
0714: * @param value
0715: * the parameter value to set
0716: */
0717: public void setParameterForNextRequest(String componentPath,
0718: Object value) {
0719: if (getLastRenderedPage() == null) {
0720: Assert
0721: .fail("before using this method, at least one page has to be rendered");
0722: }
0723:
0724: Component c = getComponentFromLastRenderedPage(componentPath);
0725: if (c == null) {
0726: Assert
0727: .fail("component " + componentPath
0728: + " was not found");
0729: return;
0730: }
0731:
0732: if (c instanceof FormComponent) {
0733: getParametersForNextRequest().put(
0734: ((FormComponent) c).getInputName(), value);
0735: } else {
0736: getParametersForNextRequest().put(c.getPath(), value);
0737: }
0738:
0739: }
0740:
0741: /**
0742: * assert last rendered Page class
0743: *
0744: * @param expectedReneredPageClass
0745: * expected class of last renered page
0746: */
0747: public void assertRenderedPage(Class expectedReneredPageClass) {
0748: if (!getLastRenderedPage().getClass().isAssignableFrom(
0749: expectedReneredPageClass)) {
0750: Assert.assertEquals(Classes
0751: .simpleName(expectedReneredPageClass), Classes
0752: .simpleName(getLastRenderedPage().getClass()));
0753: }
0754: }
0755:
0756: /**
0757: * assert no error feedback messages
0758: */
0759: public void assertNoErrorMessage() {
0760: List messages = getMessages(FeedbackMessage.ERROR);
0761: Assert.assertTrue("expect no error message, but contains\n"
0762: + WicketTesterHelper.asLined(messages), messages
0763: .isEmpty());
0764: }
0765:
0766: /**
0767: * assert no info feedback messages
0768: */
0769: public void assertNoInfoMessage() {
0770: List messages = getMessages(FeedbackMessage.INFO);
0771: Assert.assertTrue("expect no info message, but contains\n"
0772: + WicketTesterHelper.asLined(messages), messages
0773: .isEmpty());
0774: }
0775:
0776: /**
0777: * assert error feedback messages
0778: *
0779: * @param expectedErrorMessages
0780: * expected error messages
0781: */
0782: public void assertErrorMessages(String[] expectedErrorMessages) {
0783: List actualMessages = getMessages(FeedbackMessage.ERROR);
0784: WicketTesterHelper.assertEquals(Arrays
0785: .asList(expectedErrorMessages), actualMessages);
0786: }
0787:
0788: /**
0789: * assert info feedback message
0790: *
0791: * @param expectedInfoMessages
0792: * expected info messages
0793: */
0794: public void assertInfoMessages(String[] expectedInfoMessages) {
0795: List actualMessages = getMessages(FeedbackMessage.INFO);
0796: WicketTesterHelper.assertEquals(Arrays
0797: .asList(expectedInfoMessages), actualMessages);
0798: }
0799:
0800: /**
0801: * get feedback messages
0802: *
0803: * @param level
0804: * level of feedback message, ex.
0805: * <code>FeedbackMessage.DEBUG or FeedbackMessage.INFO.. etc</code>
0806: * @return List list of messages (in String)
0807: * @see FeedbackMessage
0808: */
0809: public List getMessages(final int level) {
0810: FeedbackMessages feedbackMessages = getLastRenderedPage()
0811: .getFeedbackMessages();
0812: List allMessages = feedbackMessages
0813: .messages(new IFeedbackMessageFilter() {
0814: private static final long serialVersionUID = 1L;
0815:
0816: public boolean accept(FeedbackMessage message) {
0817: return message.getLevel() == level;
0818: }
0819: });
0820: List actualMessages = new ArrayList();
0821: for (Iterator iter = allMessages.iterator(); iter.hasNext();) {
0822: actualMessages.add(((FeedbackMessage) iter.next())
0823: .getMessage());
0824: }
0825: return actualMessages;
0826: }
0827:
0828: /**
0829: * assert previous rendered page expired
0830: *
0831: * TODO Post 1.2: General: This test is no longer valid because it depends
0832: * on an implementation detail that just changed!
0833: *
0834: * public void assertExpirePreviousPage() { PageMap pageMap =
0835: * getWicketSession().getPageMap(null); Field internalMapCacheField; try {
0836: * internalMapCacheField = pageMap.getClass().getDeclaredField("pages");
0837: * internalMapCacheField.setAccessible(true); MostRecentlyUsedMap mru =
0838: * (MostRecentlyUsedMap)internalMapCacheField.get(pageMap);
0839: * Assert.assertFalse("Previous Page '" +
0840: * Classes.name(getPreviousRenderedPage().getClass()) + "' not expire", mru
0841: * .containsValue(getPreviousRenderedPage())); } catch (SecurityException e) {
0842: * throw convertoUnexpect(e); } catch (NoSuchFieldException e) { throw
0843: * convertoUnexpect(e); } catch (IllegalAccessException e) { throw
0844: * convertoUnexpect(e); } }
0845: */
0846:
0847: /**
0848: * dump the source of last rendered page
0849: */
0850: public void dumpPage() {
0851: log.info(getServletResponse().getDocument());
0852: }
0853:
0854: /**
0855: * dump component tree
0856: */
0857: public void debugComponentTrees() {
0858: debugComponentTrees("");
0859: }
0860:
0861: /**
0862: * Dump the component trees to log.
0863: *
0864: * @param filter
0865: * Show only the components, which path contains the
0866: * filterstring.
0867: */
0868: public void debugComponentTrees(String filter) {
0869: log
0870: .info("debugging ----------------------------------------------");
0871: for (Iterator iter = WicketTesterHelper.getComponentData(
0872: getLastRenderedPage()).iterator(); iter.hasNext();) {
0873: WicketTesterHelper.ComponentData obj = (WicketTesterHelper.ComponentData) iter
0874: .next();
0875: if (obj.path.matches(".*" + filter + ".*")) {
0876: log.info("path\t" + obj.path + " \t" + obj.type
0877: + " \t[" + obj.value + "]");
0878: }
0879: }
0880: }
0881:
0882: /**
0883: * Test that a component has been added to a AjaxRequestTarget, using
0884: * {@link AjaxRequestTarget#addComponent(Component)}. This method actually
0885: * tests that a component is on the AJAX response sent back to the client.
0886: * <p>
0887: * PLEASE NOTE! This method doesn't actually insert the component in the
0888: * client DOM tree, using javascript. But it shouldn't be needed because you
0889: * have to trust that the Wicket Ajax Javascript just works.
0890: *
0891: * @param component
0892: * The component to test whether it's on the response.
0893: */
0894: public void assertComponentOnAjaxResponse(Component component) {
0895: String failMessage = "A component which is null could not have been added to the AJAX response";
0896: Assert.assertNotNull(failMessage, component);
0897:
0898: // Get the AJAX response
0899: String ajaxResponse = getServletResponse().getDocument();
0900:
0901: // Test that the previous response was actually a AJAX response
0902: failMessage = "The Previous response was not an AJAX response. "
0903: + "You need to execute an AJAX event, using clickLink, before using this assert";
0904: boolean isAjaxResponse = ajaxResponse
0905: .startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ajax-response>");
0906: Assert.assertTrue(failMessage, isAjaxResponse);
0907:
0908: // See if the component has a markup id
0909: String markupId = component.getMarkupId();
0910:
0911: failMessage = "The component doesn't have a markup id, "
0912: + "which means that it can't have been added to the AJAX response";
0913: Assert.assertFalse(failMessage, Strings.isEmpty(markupId));
0914:
0915: // Look for that the component is on the response, using the markup id
0916: boolean isComponentInAjaxResponse = ajaxResponse
0917: .matches(".*<component id=\"" + markupId + "\" ?>.*");
0918: failMessage = "Component wasn't found in the AJAX response";
0919: Assert.assertTrue(failMessage, isComponentInAjaxResponse);
0920: }
0921:
0922: /**
0923: * Simulate that an AJAX event has been fired.
0924: *
0925: * @see #executeAjaxEvent(Component, String)
0926: *
0927: * @since 1.2.3
0928: * @param componentPath
0929: * The component path.
0930: * @param event
0931: * The event which we simulate is fired. If the event is null,
0932: * the test will fail.
0933: */
0934: public void executeAjaxEvent(String componentPath, String event) {
0935: Component component = getComponentFromLastRenderedPage(componentPath);
0936: executeAjaxEvent(component, event);
0937: }
0938:
0939: /**
0940: * Simulate that an AJAX event has been fired. You add an AJAX event to a
0941: * component by using:
0942: *
0943: * <pre>
0944: * ...
0945: * component.add(new AjaxEventBehavior("ondblclick") {
0946: * public void onEvent(AjaxRequestTarget) {
0947: * // Do something.
0948: * }
0949: * });
0950: * ...
0951: * </pre>
0952: *
0953: * You can then test that the code inside onEvent actually does what it's
0954: * supposed to, using the WicketTester:
0955: *
0956: * <pre>
0957: * ...
0958: * tester.executeAjaxEvent(component, "ondblclick");
0959: *
0960: * // Test that the code inside onEvent is correct.
0961: * ...
0962: * </pre>
0963: *
0964: * This also works with AjaxFormSubmitBehavior, where it will "submit" the
0965: * form before executing the command.
0966: * <p>
0967: * PLEASE NOTE! This method doesn't actually insert the component in the
0968: * client DOM tree, using javascript.
0969: *
0970: *
0971: * @param component
0972: * The component which has the AjaxEventBehavior we wan't to
0973: * test. If the component is null, the test will fail.
0974: * @param event
0975: * The event which we simulate is fired. If the event is null,
0976: * the test will fail.
0977: */
0978: public void executeAjaxEvent(Component component, String event) {
0979: String failMessage = "Can't execute event on a component which is null.";
0980: Assert.assertNotNull(failMessage, component);
0981:
0982: failMessage = "event must not be null";
0983: Assert.assertNotNull(failMessage, event);
0984:
0985: // Run through all the behavior and select the LAST ADDED behavior which
0986: // matches the event parameter.
0987: AjaxEventBehavior ajaxEventBehavior = null;
0988: List behaviors = component.getBehaviors();
0989: for (Iterator iter = behaviors.iterator(); iter.hasNext();) {
0990: IBehavior behavior = (IBehavior) iter.next();
0991:
0992: // AjaxEventBehavior is the one to look for
0993: if (behavior instanceof AjaxEventBehavior) {
0994: AjaxEventBehavior tmp = (AjaxEventBehavior) behavior;
0995:
0996: if (event.equals(tmp.getEvent())) {
0997: ajaxEventBehavior = tmp;
0998: }
0999: }
1000: }
1001:
1002: // If there haven't been found any event behaviors on the component
1003: // which maches the parameters we fail.
1004: failMessage = "No AjaxEventBehavior found on component: "
1005: + component.getId() + " which matches the event: "
1006: + event.toString();
1007: Assert.assertNotNull(failMessage, ajaxEventBehavior);
1008:
1009: setupRequestAndResponse();
1010: RequestCycle requestCycle = createRequestCycle();
1011:
1012: // If the event is an FormSubmitBehavior then also "submit" the form
1013: if (ajaxEventBehavior instanceof AjaxFormSubmitBehavior) {
1014: AjaxFormSubmitBehavior ajaxFormSubmitBehavior = (AjaxFormSubmitBehavior) ajaxEventBehavior;
1015: submitAjaxFormSubmitBehavior(ajaxFormSubmitBehavior);
1016: }
1017:
1018: ajaxEventBehavior.onRequest();
1019:
1020: // process the request target
1021: requestCycle.getRequestTarget().respond(requestCycle);
1022: }
1023:
1024: /**
1025: * Get a TagTester based on a wicket:id. If more components exists with the
1026: * same wicket:id in the markup only the first one is returned.
1027: *
1028: * @param wicketId
1029: * The wicket:id to search for.
1030: * @return The TagTester for the tag which has the given wicket:id.
1031: */
1032: public TagTester getTagByWicketId(String wicketId) {
1033: return TagTester.createTagByAttribute(getServletResponse()
1034: .getDocument(), "wicket:id", wicketId);
1035: }
1036:
1037: /**
1038: * Get a TagTester based on an dom id. If more components exists with the
1039: * same id in the markup only the first one is returned.
1040: *
1041: * @param id
1042: * The dom id to search for.
1043: * @return The TagTester for the tag which has the given dom id.
1044: */
1045: public TagTester getTagById(String id) {
1046: return TagTester.createTagByAttribute(getServletResponse()
1047: .getDocument(), "id", id);
1048: }
1049:
1050: /**
1051: * Helper method for all the places where an AjaxCall should submit an
1052: * associated form.
1053: *
1054: * @param behavior
1055: * The AjaxFormSubmitBehavior with the form to "submit"
1056: */
1057: private void submitAjaxFormSubmitBehavior(
1058: AjaxFormSubmitBehavior behavior) {
1059: // We need to get the form submitted, using reflection.
1060: // It needs to be "submitted".
1061: Form form = null;
1062: try {
1063: Field formField = AjaxFormSubmitBehavior.class
1064: .getDeclaredField("form");
1065: formField.setAccessible(true);
1066: form = (Form) formField.get(behavior);
1067: } catch (Exception e) {
1068: Assert.fail(e.getMessage());
1069: }
1070:
1071: String failMessage = "No form attached to the submitlink.";
1072: Assert.assertNotNull(failMessage, form);
1073:
1074: form.visitFormComponents(new FormComponent.IVisitor() {
1075: public void formComponent(FormComponent formComponent) {
1076: if (!(formComponent instanceof Button)
1077: && !(formComponent instanceof RadioGroup)
1078: && !(formComponent instanceof CheckGroup)) {
1079: String name = formComponent.getInputName();
1080: String value = formComponent.getValue();
1081:
1082: getServletRequest().setParameter(name, value);
1083: }
1084: }
1085: });
1086: }
1087: }
|