001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.util.tester; 018 019import static org.junit.jupiter.api.Assertions.assertNotNull; 020 021import java.lang.reflect.InvocationTargetException; 022import java.lang.reflect.Method; 023import java.util.ArrayList; 024import java.util.HashSet; 025import java.util.List; 026import java.util.Objects; 027 028import org.apache.wicket.Component; 029import org.apache.wicket.WicketRuntimeException; 030import org.apache.wicket.ajax.markup.html.form.AjaxButton; 031import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; 032import org.apache.wicket.markup.html.form.AbstractSingleSelectChoice; 033import org.apache.wicket.markup.html.form.AbstractTextComponent; 034import org.apache.wicket.markup.html.form.Check; 035import org.apache.wicket.markup.html.form.CheckGroup; 036import org.apache.wicket.markup.html.form.Form; 037import org.apache.wicket.markup.html.form.FormComponent; 038import org.apache.wicket.markup.html.form.FormComponentUpdatingBehavior; 039import org.apache.wicket.markup.html.form.IChoiceRenderer; 040import org.apache.wicket.markup.html.form.IFormSubmittingComponent; 041import org.apache.wicket.markup.html.form.ListMultipleChoice; 042import org.apache.wicket.markup.html.form.Radio; 043import org.apache.wicket.markup.html.form.RadioGroup; 044import org.apache.wicket.markup.html.form.upload.FileUploadField; 045import org.apache.wicket.markup.html.form.upload.MultiFileUploadField; 046import org.apache.wicket.protocol.http.mock.MockHttpServletRequest; 047import org.apache.wicket.util.file.File; 048import org.apache.wicket.util.lang.Args; 049import org.apache.wicket.util.string.StringValue; 050import org.apache.wicket.util.string.Strings; 051import org.apache.wicket.util.visit.IVisit; 052import org.apache.wicket.util.visit.IVisitor; 053 054/** 055 * A helper class for testing validation and submission of <code>FormComponent</code>s. 056 * 057 * @author Ingram Chen 058 * @author Frank Bille (frankbille) 059 * @since 1.2.6 060 */ 061public class FormTester 062{ 063 private static final String NO_FORM_FOR_PATH = "No Form componet found for path '%s'. Check if path value is correct and " 064 + "if form component is visible and active."; 065 066 /** 067 * An auto incrementing index used as a suffix for MultiFileUploadField's inputName 068 */ 069 private int multiFileUploadIndex = 0; 070 071 /** 072 * A selector template for selecting selectable <code>FormComponent</code>s with an index of 073 * option -- supports <code>RadioGroup</code>, <code>CheckGroup</code>, and 074 * <code>AbstractChoice</code> family. 075 */ 076 protected abstract class ChoiceSelector 077 { 078 /** 079 * TODO need Javadoc from author. 080 */ 081 private final class SearchOptionByIndexVisitor implements IVisitor<Component, Component> 082 { 083 int count = 0; 084 085 private final int index; 086 087 private SearchOptionByIndexVisitor(int index) 088 { 089 super(); 090 this.index = index; 091 } 092 093 /** 094 * @see org.apache.wicket.util.visit.IVisitor#component(Object, 095 * org.apache.wicket.util.visit.IVisit) 096 */ 097 @Override 098 public void component(final Component component, final IVisit<Component> visit) 099 { 100 if (count == index) 101 { 102 visit.stop(component); 103 } 104 else 105 { 106 count++; 107 } 108 } 109 } 110 111 private final FormComponent<?> formComponent; 112 113 /** 114 * Constructor. 115 * 116 * @param formComponent 117 * a <code>FormComponent</code> 118 */ 119 protected ChoiceSelector(FormComponent<?> formComponent) 120 { 121 this.formComponent = formComponent; 122 } 123 124 /** 125 * Implements whether toggle or accumulate the selection. 126 * 127 * @param formComponent 128 * a <code>FormComponent</code> 129 * @param value 130 * a <code>String</code> value 131 */ 132 protected abstract void assignValueToFormComponent(FormComponent<?> formComponent, 133 String value); 134 135 public String getChoiceValueForIndex(int index) 136 { 137 if (formComponent instanceof RadioGroup) 138 { 139 Radio<?> foundRadio = (Radio<?>)formComponent.visitChildren(Radio.class, 140 new SearchOptionByIndexVisitor(index)); 141 if (foundRadio == null) 142 { 143 fail("RadioGroup " + formComponent.getPath() + " does not have index:" + index); 144 return null; 145 } 146 return foundRadio.getValue(); 147 } 148 else if (formComponent instanceof CheckGroup) 149 { 150 Check<?> foundCheck = (Check<?>)formComponent.visitChildren(Check.class, 151 new SearchOptionByIndexVisitor(index)); 152 if (foundCheck == null) 153 { 154 fail("CheckGroup " + formComponent.getPath() + " does not have index:" + index); 155 return null; 156 } 157 158 return foundCheck.getValue(); 159 } 160 else 161 { 162 String idValue = selectAbstractChoice(formComponent, index); 163 if (idValue == null) 164 { 165 fail(formComponent.getPath() + " is not a selectable Component."); 166 return null; 167 } 168 else 169 { 170 return idValue; 171 } 172 } 173 174 } 175 176 /** 177 * Selects a given index in a selectable <code>FormComponent</code>. 178 * 179 * @param index 180 */ 181 protected final void doSelect(final int index) 182 { 183 String value = getChoiceValueForIndex(index); 184 assignValueToFormComponent(formComponent, value); 185 } 186 187 /** 188 * Selects a given index in a selectable <code>FormComponent</code>. 189 * 190 * @param formComponent 191 * a <code>FormComponent</code> 192 * @param index 193 * the index to select 194 * @return the id value at the selected index 195 */ 196 @SuppressWarnings("unchecked") 197 private String selectAbstractChoice(final FormComponent<?> formComponent, final int index) 198 { 199 try 200 { 201 Method getChoicesMethod = formComponent.getClass().getMethod("getChoices", 202 (Class<?>[])null); 203 getChoicesMethod.setAccessible(true); 204 List<Object> choices = (List<Object>)getChoicesMethod.invoke(formComponent, 205 (Object[])null); 206 207 Method getChoiceRendererMethod = formComponent.getClass().getMethod( 208 "getChoiceRenderer", (Class<?>[])null); 209 getChoiceRendererMethod.setAccessible(true); 210 IChoiceRenderer<Object> choiceRenderer = (IChoiceRenderer<Object>)getChoiceRendererMethod.invoke( 211 formComponent, (Object[])null); 212 213 return choiceRenderer.getIdValue(choices.get(index), index); 214 } 215 catch (SecurityException e) 216 { 217 throw new WicketRuntimeException("unexpect select failure", e); 218 } 219 catch (NoSuchMethodException e) 220 { 221 // component without getChoices() or getChoiceRenderer() is not 222 // selectable 223 return null; 224 } 225 catch (IllegalAccessException e) 226 { 227 throw new WicketRuntimeException("unexpect select failure", e); 228 } 229 catch (InvocationTargetException e) 230 { 231 throw new WicketRuntimeException("unexpect select failure", e); 232 } 233 } 234 } 235 236 /** 237 * A factory that creates an appropriate <code>ChoiceSelector</code> based on type of 238 * <code>FormComponent</code>. 239 */ 240 private class ChoiceSelectorFactory 241 { 242 /** 243 * <code>MultipleChoiceSelector</code> class. 244 */ 245 private final class MultipleChoiceSelector extends ChoiceSelector 246 { 247 /** 248 * Constructor. 249 * 250 * @param formComponent 251 * a <code>FormComponent</code> 252 */ 253 protected MultipleChoiceSelector(FormComponent<?> formComponent) 254 { 255 super(formComponent); 256 if (!allowMultipleChoice(formComponent)) 257 { 258 fail("Component:'" + formComponent.getPath() + 259 "' Does not support multiple selection."); 260 } 261 } 262 263 @Override 264 protected void assignValueToFormComponent(FormComponent<?> formComponent, String value) 265 { 266 // multiple selectable should retain selected option 267 addFormComponentValue(formComponent, value); 268 } 269 } 270 271 /** 272 * <code>SingleChoiceSelector</code> class. 273 */ 274 private final class SingleChoiceSelector extends ChoiceSelector 275 { 276 /** 277 * Constructor. 278 * 279 * @param formComponent 280 * a <code>FormComponent</code> 281 */ 282 protected SingleChoiceSelector(FormComponent<?> formComponent) 283 { 284 super(formComponent); 285 } 286 287 @Override 288 protected void assignValueToFormComponent(FormComponent<?> formComponent, String value) 289 { 290 // single selectable should overwrite already selected option 291 setFormComponentValue(formComponent, value); 292 } 293 } 294 295 /** 296 * Creates a <code>ChoiceSelector</code>. 297 * 298 * @param formComponent 299 * a <code>FormComponent</code> 300 * @return ChoiceSelector a <code>ChoiceSelector</code> 301 */ 302 protected ChoiceSelector create(FormComponent<?> formComponent) 303 { 304 if (formComponent == null) 305 { 306 fail("Trying to select on null component."); 307 return null; 308 } 309 else if (formComponent instanceof RadioGroup || 310 formComponent instanceof AbstractSingleSelectChoice) 311 { 312 return new SingleChoiceSelector(formComponent); 313 } 314 else if (allowMultipleChoice(formComponent)) 315 { 316 return new MultipleChoiceSelector(formComponent); 317 } 318 else 319 { 320 fail("Selecting on the component:'" + formComponent.getPath() + 321 "' is not supported."); 322 return null; 323 } 324 } 325 326 /** 327 * Creates a <code>MultipleChoiceSelector</code>. 328 * 329 * @param formComponent 330 * a <code>FormComponent</code> 331 * @return ChoiceSelector a <code>ChoiceSelector</code> 332 */ 333 protected ChoiceSelector createForMultiple(FormComponent<?> formComponent) 334 { 335 return new MultipleChoiceSelector(formComponent); 336 } 337 338 /** 339 * Tests if a given <code>FormComponent</code> allows multiple choice. 340 * 341 * @param formComponent 342 * a <code>FormComponent</code> 343 * @return <code>true</code> if the given FormComponent allows multiple choice 344 */ 345 private boolean allowMultipleChoice(FormComponent<?> formComponent) 346 { 347 return formComponent instanceof CheckGroup || 348 formComponent instanceof ListMultipleChoice; 349 } 350 } 351 352 private final ChoiceSelectorFactory choiceSelectorFactory = new ChoiceSelectorFactory(); 353 354 /** 355 * An instance of <code>FormTester</code> can only be used once. Create a new instance of each 356 * test. 357 */ 358 private boolean closed = false; 359 360 /** path to <code>FormComponent</code> */ 361 private final String path; 362 363 /** <code>BaseWicketTester</code> that create <code>FormTester</code> */ 364 private final BaseWicketTester tester; 365 366 /** <code>FormComponent</code> to be tested */ 367 private final Form<?> workingForm; 368 369 private boolean clearFeedbackMessagesBeforeSubmit = true; 370 371 /** 372 * @see WicketTester#newFormTester(String) 373 * 374 * @param path 375 * path to <code>FormComponent</code> 376 * @param workingForm 377 * <code>FormComponent</code> to be tested 378 * @param wicketTester 379 * <code>WicketTester</code> that creates <code>FormTester</code> 380 * @param fillBlankString 381 * specifies whether to fill child <code>TextComponent</code>s with blank 382 * <code>String</code>s 383 */ 384 protected FormTester(final String path, final Form<?> workingForm, 385 final BaseWicketTester wicketTester, final boolean fillBlankString) 386 { 387 this.workingForm = Objects.requireNonNull(workingForm, String.format(NO_FORM_FOR_PATH, path)); 388 this.tester = wicketTester; 389 this.path = path; 390 391 // fill blank String for Text Component. 392 workingForm.visitFormComponents(new IVisitor<FormComponent<?>, Void>() 393 { 394 @Override 395 public void component(final FormComponent<?> formComponent, final IVisit<Void> visit) 396 { 397 // do nothing for invisible or disabled component -- the browser would not send any 398 // parameter for a disabled component 399 if (!(formComponent.isVisibleInHierarchy() && formComponent.isEnabledInHierarchy())) 400 { 401 return; 402 } 403 404 String[] values = getInputValue(formComponent); 405 if (formComponent instanceof AbstractTextComponent<?>) 406 { 407 if (values.length == 0 && fillBlankString) 408 { 409 setFormComponentValue(formComponent, ""); 410 } 411 } 412 for (String value : values) 413 { 414 addFormComponentValue(formComponent, value); 415 } 416 } 417 }); 418 workingForm.detach(); 419 } 420 421 /** 422 * Gets request parameter values for the form component that represents its current model value 423 * 424 * @param formComponent 425 * @return array containing parameter values 426 */ 427 public static String[] getInputValue(FormComponent<?> formComponent) 428 { 429 // the browser sends parameters for visible and enabled components only 430 if (formComponent.isVisibleInHierarchy() && formComponent.isEnabledInHierarchy()) 431 { 432 if (formComponent instanceof IFormSubmittingComponent) 433 { 434 // buttons have to be submitted explicitely 435 } 436 else if (formComponent instanceof AbstractTextComponent) 437 { 438 return new String[] { getFormComponentValue(formComponent) }; 439 } 440 else 441 { 442 // TODO is it safe to assume that all other components' values can be split? 443 String value = getFormComponentValue(formComponent); 444 if (!Strings.isEmpty(value)) 445 { 446 return value.split(FormComponent.VALUE_SEPARATOR); 447 } 448 } 449 } 450 return new String[] { }; 451 } 452 453 454 private static String getFormComponentValue(final FormComponent<?> formComponent) 455 { 456 boolean oldEscape = formComponent.getEscapeModelStrings(); 457 formComponent.setEscapeModelStrings(false); 458 String val = formComponent.getValue(); 459 formComponent.setEscapeModelStrings(oldEscape); 460 return val; 461 } 462 463 /** 464 * Retrieves the current <code>Form</code> object. 465 * 466 * @return the working <code>Form</code> 467 */ 468 public Form<?> getForm() 469 { 470 return workingForm; 471 } 472 473 /** 474 * Gets the value for an <code>AbstractTextComponent</code> with the provided id. 475 * 476 * @param id 477 * <code>Component</code> id 478 * @return the value of the text component 479 */ 480 public String getTextComponentValue(final String id) 481 { 482 Component c = getForm().get(id); 483 if (c instanceof AbstractTextComponent) 484 { 485 return ((AbstractTextComponent<?>)c).getValue(); 486 } 487 return null; 488 } 489 490 /** 491 * Simulates selecting an option of a <code>FormComponent</code>. Supports 492 * <code>RadioGroup</code>, <code>CheckGroup</code>, and <code>AbstractChoice</code> family 493 * currently. The behavior is similar to interacting on the browser: For a single choice, such 494 * as <code>Radio</code> or <code>DropDownList</code>, the selection will toggle each other. For 495 * multiple choice, such as <code>Checkbox</code> or <code>ListMultipleChoice</code>, the 496 * selection will accumulate. 497 * 498 * @param formComponentId 499 * relative path (from <code>Form</code>) to the selectable 500 * <code>FormComponent</code> 501 * @param index 502 * index of the selectable option, starting from 0 503 * @return This 504 */ 505 public FormTester select(final String formComponentId, int index) 506 { 507 checkClosed(); 508 FormComponent<?> component = (FormComponent<?>)workingForm.get(formComponentId); 509 510 ChoiceSelector choiceSelector = choiceSelectorFactory.create(component); 511 choiceSelector.doSelect(index); 512 513 for (FormComponentUpdatingBehavior updater : component.getBehaviors(FormComponentUpdatingBehavior.class)) { 514 tester.invokeListener(component, updater); 515 } 516 517 return this; 518 } 519 520 /** 521 * A convenience method to select multiple options for the <code>FormComponent</code>. The 522 * method only support multiple selectable <code>FormComponent</code>s. 523 * 524 * @see #select(String, int) 525 * 526 * @param formComponentId 527 * relative path (from <code>Form</code>) to the selectable 528 * <code>FormComponent</code> 529 * @param indexes 530 * index of the selectable option, starting from 0 531 * @return This 532 */ 533 public FormTester selectMultiple(String formComponentId, int[] indexes) 534 { 535 return selectMultiple(formComponentId, indexes, false); 536 } 537 538 /** 539 * A convenience method to select multiple options for the <code>FormComponent</code>. The 540 * method only support multiple selectable <code>FormComponent</code>s. 541 * 542 * @see #select(String, int) 543 * 544 * @param formComponentId 545 * relative path (from <code>Form</code>) to the selectable 546 * <code>FormComponent</code> 547 * @param indexes 548 * index of the selectable option, starting from 0 549 * @param replace 550 * If true, than all previous selects are first reset, thus existing selects are 551 * replaced. If false, than the new indexes will be added. 552 * @return This 553 */ 554 public FormTester selectMultiple(String formComponentId, int[] indexes, final boolean replace) 555 { 556 checkClosed(); 557 558 if (replace) 559 { 560 // Reset first 561 setValue(formComponentId, ""); 562 } 563 564 ChoiceSelector choiceSelector = choiceSelectorFactory.createForMultiple((FormComponent<?>)workingForm.get(formComponentId)); 565 566 for (int index : indexes) 567 { 568 choiceSelector.doSelect(index); 569 } 570 571 return this; 572 } 573 574 /** 575 * Simulates filling in a field on a <code>Form</code>. 576 * 577 * @param formComponentId 578 * relative path (from <code>Form</code>) to the selectable 579 * <code>FormComponent</code> or <code>IFormSubmittingComponent</code> 580 * @param value 581 * the field value 582 * @return This 583 */ 584 public FormTester setValue(final String formComponentId, final String value) 585 { 586 Component component = workingForm.get(formComponentId); 587 assertNotNull(component, "Unable to set value. Couldn't find component with name: " + 588 formComponentId); 589 return setValue(component, value); 590 } 591 592 /** 593 * Simulates filling in a field on a <code>Form</code>. 594 * 595 * @param formComponent 596 * relative path (from <code>Form</code>) to the selectable 597 * <code>FormComponent</code> or <code>IFormSubmittingComponent</code> 598 * @param value 599 * the field value 600 * @return This 601 */ 602 public FormTester setValue(final Component formComponent, final String value) 603 { 604 Args.notNull(formComponent, "formComponent"); 605 606 checkClosed(); 607 608 if (formComponent instanceof IFormSubmittingComponent) 609 { 610 setFormSubmittingComponentValue((IFormSubmittingComponent)formComponent, value); 611 } 612 else if (formComponent instanceof FormComponent) 613 { 614 setFormComponentValue((FormComponent<?>)formComponent, value); 615 } 616 else 617 { 618 fail("Component with id: " + formComponent.getId() + " is not a FormComponent"); 619 } 620 621 return this; 622 } 623 624 /** 625 * @param checkBoxId 626 * @param value 627 * @return This 628 */ 629 public FormTester setValue(String checkBoxId, boolean value) 630 { 631 return setValue(checkBoxId, Boolean.toString(value)); 632 } 633 634 /** 635 * Sets the <code>File</code> on a {@link FileUploadField}. 636 * 637 * @param formComponentId 638 * relative path (from <code>Form</code>) to the selectable 639 * <code>FormComponent</code>. The <code>FormComponent</code> must be of a type 640 * <code>FileUploadField</code>. 641 * @param file 642 * the <code>File</code> to upload or {@code null} for an empty input 643 * @param contentType 644 * the content type of the file. Must be a valid mime type. 645 * @return This 646 */ 647 public FormTester setFile(final String formComponentId, final File file, 648 final String contentType) 649 { 650 checkClosed(); 651 652 FormComponent<?> formComponent = (FormComponent<?>)workingForm.get(formComponentId); 653 654 MockHttpServletRequest servletRequest = tester.getRequest(); 655 656 if (formComponent instanceof FileUploadField) 657 { 658 servletRequest.addFile(formComponent.getInputName(), file, contentType); 659 } 660 else if (formComponent instanceof MultiFileUploadField) 661 { 662 String inputName = formComponent.getInputName() + MultiFileUploadField.MAGIC_SEPARATOR + multiFileUploadIndex++; 663 servletRequest.addFile(inputName, file, contentType); 664 } 665 else 666 { 667 fail("'" + formComponentId + "' is not " + 668 "a FileUploadField. You can only attach a file to form " + 669 "component of this type."); 670 } 671 672 return this; 673 } 674 675 /** 676 * Submits the <code>Form</code>. Note that <code>submit</code> can be executed only once. 677 * 678 * @return This 679 */ 680 public FormTester submit() 681 { 682 checkClosed(); 683 try 684 { 685 if (clearFeedbackMessagesBeforeSubmit) 686 { 687 tester.clearFeedbackMessages(); 688 } 689 tester.getRequest().setUseMultiPartContentType(workingForm.isMultiPart()); 690 tester.submitForm(path); 691 } 692 finally 693 { 694 closed = true; 695 } 696 697 return this; 698 } 699 700 public boolean isClearFeedbackMessagesBeforeSubmit() 701 { 702 return clearFeedbackMessagesBeforeSubmit; 703 } 704 705 public FormTester setClearFeedbackMessagesBeforeSubmit(boolean clearFeedbackMessagesBeforeSubmit) 706 { 707 this.clearFeedbackMessagesBeforeSubmit = clearFeedbackMessagesBeforeSubmit; 708 return this; 709 } 710 711 /** 712 * A convenience method for submitting the <code>Form</code> with an alternate button. 713 * <p> 714 * Note that if the button is associated with a model, it's better to use the 715 * <code>setValue</code> method instead: 716 * 717 * <pre> 718 * formTester.setValue("to:my:button", "value on the button"); 719 * formTester.submit(); 720 * </pre> 721 * 722 * @param buttonComponentId 723 * relative path (from <code>Form</code>) to the button 724 * @return This 725 */ 726 public FormTester submit(final String buttonComponentId) 727 { 728 Component submitter = getForm().get(buttonComponentId); 729 if (submitter == null) 730 { 731 fail("Cannot submit the form because there is no submitting component with id: " + buttonComponentId); 732 } 733 734 return submit(submitter); 735 } 736 737 /** 738 * A convenience method for submitting the <code>Form</code> with an alternate button. 739 * <p> 740 * Note that if the button is associated with a model, it's better to use the 741 * <code>setValue</code> method instead: 742 * 743 * <pre> 744 * formTester.setValue(myButton, "value on the button"); 745 * formTester.submit(); 746 * </pre> 747 * 748 * @param buttonComponent 749 * relative path (from <code>Form</code>) to the button 750 * @return This 751 */ 752 public FormTester submit(final Component buttonComponent) 753 { 754 Args.notNull(buttonComponent, "buttonComponent"); 755 756 setValue(buttonComponent, "marked"); 757 758 if (buttonComponent instanceof AjaxButton || buttonComponent instanceof AjaxSubmitLink) 759 { 760 if (clearFeedbackMessagesBeforeSubmit) 761 { 762 tester.clearFeedbackMessages(); 763 } 764 tester.getRequest().setUseMultiPartContentType(workingForm.isMultiPart()); 765 tester.executeAjaxEvent(buttonComponent, "click"); 766 return this; 767 } 768 else 769 { 770 return submit(); 771 } 772 } 773 774 /** 775 * A convenience method to submit the Form via a SubmitLink which may inside or outside of the 776 * Form. 777 * 778 * @param path 779 * The path to the SubmitLink 780 * @param pageRelative 781 * if true, than the 'path' to the SubmitLink is relative to the page. Thus the link 782 * can be outside the form. If false, the path is relative to the form and thus the 783 * link is inside the form. 784 * @return This 785 */ 786 public FormTester submitLink(String path, final boolean pageRelative) 787 { 788 if (pageRelative) 789 { 790 tester.clickLink(path, false); 791 } 792 else 793 { 794 path = this.path + ":" + path; 795 tester.clickLink(path, false); 796 } 797 return this; 798 } 799 800 /** 801 * Adds an additional <code>FormComponent</code>'s value into request parameter -- this method 802 * retains existing parameters but removes any duplicate parameters. 803 * 804 * @param formComponent 805 * a <code>FormComponent</code> 806 * @param value 807 * a value to add 808 * @return This 809 */ 810 private FormTester addFormComponentValue(FormComponent<?> formComponent, String value) 811 { 812 if (parameterExist(formComponent)) 813 { 814 List<StringValue> values = tester.getRequest() 815 .getPostParameters() 816 .getParameterValues(formComponent.getInputName()); 817 // remove duplicated 818 819 HashSet<String> all = new HashSet<String>(); 820 for (StringValue val : values) 821 { 822 all.add(val.toString()); 823 } 824 all.add(value); 825 826 values = new ArrayList<StringValue>(); 827 for (String val : all) 828 { 829 values.add(StringValue.valueOf(val)); 830 } 831 tester.getRequest() 832 .getPostParameters() 833 .setParameterValues(formComponent.getInputName(), values); 834 } 835 else 836 { 837 setFormComponentValue(formComponent, value); 838 } 839 840 return this; 841 } 842 843 /** 844 * <code>FormTester</code> must only be used once. Create a new instance of 845 * <code>FormTester</code> for each test. 846 */ 847 private void checkClosed() 848 { 849 if (closed) 850 { 851 fail("'" + path + "' already submitted. Note that FormTester " + 852 "is allowed to submit only once"); 853 } 854 } 855 856 /** 857 * Returns <code>true</code> if the parameter exists in the <code>FormComponent</code>. 858 * 859 * @param formComponent 860 * a <code>FormComponent</code> 861 * @return <code>true</code> if the parameter exists in the <code>FormComponent</code> 862 */ 863 private boolean parameterExist(final FormComponent<?> formComponent) 864 { 865 String parameter = tester.getRequest() 866 .getPostParameters() 867 .getParameterValue(formComponent.getInputName()) 868 .toString(); 869 870 return parameter != null && parameter.trim().length() > 0; 871 } 872 873 /** 874 * Set formComponent's value into request parameter, this method overwrites existing parameters. 875 * 876 * @param formComponent 877 * a <code>FormComponent</code> 878 * @param value 879 * a value to add 880 */ 881 private void setFormComponentValue(final FormComponent<?> formComponent, final String value) 882 { 883 tester.getRequest() 884 .getPostParameters() 885 .setParameterValue(formComponent.getInputName(), value); 886 } 887 888 /** 889 * Set component's value into request parameter, this method overwrites existing parameters. 890 * 891 * @param component 892 * an {@link IFormSubmittingComponent} 893 * @param value 894 * a value to add 895 */ 896 private void setFormSubmittingComponentValue(IFormSubmittingComponent component, String value) 897 { 898 tester.getRequest().getPostParameters().setParameterValue(component.getInputName(), value); 899 } 900 901 /** 902 * 903 * @param message 904 */ 905 private void fail(String message) 906 { 907 throw new WicketRuntimeException(message); 908 } 909}