001 /* 002 * Created on Jan 21, 2008 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 * 014 * Copyright @2008-2010 the original author or authors. 015 */ 016 package org.fest.swing.driver; 017 018 import static javax.swing.text.DefaultEditorKit.selectAllAction; 019 import static org.fest.assertions.Assertions.assertThat; 020 import static org.fest.assertions.Fail.fail; 021 import static org.fest.swing.driver.CommonValidations.validateCellReader; 022 import static org.fest.swing.driver.ComponentStateValidator.validateIsEnabledAndShowing; 023 import static org.fest.swing.driver.JComboBoxAccessibleEditorValidator.validateEditorIsAccessible; 024 import static org.fest.swing.driver.JComboBoxContentQuery.contents; 025 import static org.fest.swing.driver.JComboBoxEditableQuery.isEditable; 026 import static org.fest.swing.driver.JComboBoxItemCountQuery.itemCountIn; 027 import static org.fest.swing.driver.JComboBoxItemIndexValidator.validateIndex; 028 import static org.fest.swing.driver.JComboBoxMatchingItemQuery.matchingItemIndex; 029 import static org.fest.swing.driver.JComboBoxSelectedIndexQuery.selectedIndexOf; 030 import static org.fest.swing.driver.JComboBoxSelectionValueQuery.NO_SELECTION_VALUE; 031 import static org.fest.swing.driver.JComboBoxSelectionValueQuery.selection; 032 import static org.fest.swing.driver.JComboBoxSetPopupVisibleTask.setPopupVisible; 033 import static org.fest.swing.driver.JComboBoxSetSelectedIndexTask.setSelectedIndex; 034 import static org.fest.swing.driver.TextAssert.verifyThat; 035 import static org.fest.swing.edt.GuiActionRunner.execute; 036 import static org.fest.swing.exception.ActionFailedException.actionFailure; 037 import static org.fest.util.Arrays.format; 038 import static org.fest.util.Strings.concat; 039 import static org.fest.util.Strings.quote; 040 041 import java.awt.Component; 042 import java.util.regex.Pattern; 043 044 import javax.swing.*; 045 046 import org.fest.assertions.Description; 047 import org.fest.swing.annotation.RunsInCurrentThread; 048 import org.fest.swing.annotation.RunsInEDT; 049 import org.fest.swing.cell.JComboBoxCellReader; 050 import org.fest.swing.core.Robot; 051 import org.fest.swing.edt.GuiQuery; 052 import org.fest.swing.edt.GuiTask; 053 import org.fest.swing.exception.*; 054 import org.fest.swing.util.*; 055 import org.fest.util.VisibleForTesting; 056 057 /** 058 * Understands functional testing of <code>{@link JComboBox}</code>es: 059 * <ul> 060 * <li>user input simulation</li> 061 * <li>state verification</li> 062 * <li>property value query</li> 063 * </ul> 064 * This class is intended for internal use only. Please use the classes in the package 065 * <code>{@link org.fest.swing.fixture}</code> in your tests. 066 * 067 * @author Alex Ruiz 068 * @author Yvonne Wang 069 */ 070 public class JComboBoxDriver extends JComponentDriver { 071 072 private static final String EDITABLE_PROPERTY = "editable"; 073 private static final String SELECTED_INDEX_PROPERTY = "selectedIndex"; 074 075 private final JListDriver listDriver; 076 private final JComboBoxDropDownListFinder dropDownListFinder; 077 078 private JComboBoxCellReader cellReader; 079 080 /** 081 * Creates a new </code>{@link JComboBoxDriver}</code>. 082 * @param robot the robot to use to simulate user input. 083 */ 084 public JComboBoxDriver(Robot robot) { 085 super(robot); 086 listDriver = new JListDriver(robot); 087 dropDownListFinder = new JComboBoxDropDownListFinder(robot); 088 cellReader(new BasicJComboBoxCellReader()); 089 } 090 091 /** 092 * Returns an array of <code>String</code>s that represents the contents of the given <code>{@link JComboBox}</code> 093 * list. The <code>String</code> representation of each element is performed using this driver's 094 * <code>{@link JComboBoxCellReader}</code>. 095 * @param comboBox the target <code>JComboBox</code>. 096 * @return an array of <code>String</code>s that represent the contents of the given <code>JComboBox</code> list. 097 * @see #value(JComboBox, int) 098 * @see #cellReader(JComboBoxCellReader) 099 */ 100 @RunsInEDT 101 public String[] contentsOf(JComboBox comboBox) { 102 return contents(comboBox, cellReader); 103 } 104 105 /** 106 * Selects the first item matching the given text in the <code>{@link JComboBox}</code>. The text of the 107 * <code>JComboBox</code> items is obtained by this fixture's <code>{@link JComboBoxCellReader}</code>. 108 * @param comboBox the target <code>JComboBox</code>. 109 * @param value the value to match. It can be a regular expression. 110 * @throws LocationUnavailableException if an element matching the given value cannot be found. 111 * @throws IllegalStateException if the <code>JComboBox</code> is disabled. 112 * @throws IllegalStateException if the <code>JComboBox</code> is not showing on the screen. 113 * @see #cellReader(JComboBoxCellReader) 114 */ 115 @RunsInEDT 116 public void selectItem(JComboBox comboBox, String value) { 117 selectItem(comboBox, new StringTextMatcher(value)); 118 } 119 120 /** 121 * Selects the first item matching the given regular expression pattern in the <code>{@link JComboBox}</code>. The 122 * text of the <code>JComboBox</code> items is obtained by this fixture's <code>{@link JComboBoxCellReader}</code>. 123 * @param comboBox the target <code>JComboBox</code>. 124 * @param pattern the regular expression pattern to match. 125 * @throws LocationUnavailableException if an element matching the given pattern cannot be found. 126 * @throws IllegalStateException if the <code>JComboBox</code> is disabled. 127 * @throws IllegalStateException if the <code>JComboBox</code> is not showing on the screen. 128 * @throws NullPointerException if the given regular expression pattern is <code>null</code>. 129 * @see #cellReader(JComboBoxCellReader) 130 * @since 1.2 131 */ 132 @RunsInEDT 133 public void selectItem(JComboBox comboBox, Pattern pattern) { 134 selectItem(comboBox, new PatternTextMatcher(pattern)); 135 } 136 137 @RunsInEDT 138 private void selectItem(JComboBox comboBox, TextMatcher matcher) { 139 int index = matchingItemIndex(comboBox, matcher, cellReader); 140 if (index < 0) throw failMatchingNotFound(comboBox, matcher); 141 selectItem(comboBox, index); 142 } 143 144 private LocationUnavailableException failMatchingNotFound(JComboBox comboBox, TextMatcher matcher) { 145 throw new LocationUnavailableException(concat( 146 "Unable to find item matching the ", matcher.description(), " ", matcher.formattedValues(), 147 " among the JComboBox contents (", format(contentsOf(comboBox)), ")")); 148 } 149 150 /** 151 * Verifies that the <code>String</code> representation of the selected item in the <code>{@link JComboBox}</code> 152 * matches the given text. 153 * @param comboBox the target <code>JComboBox</code>. 154 * @param value the text to match. It can be a regular expression. 155 * @throws AssertionError if the selected item does not match the given value. 156 * @see #cellReader(JComboBoxCellReader) 157 */ 158 @RunsInEDT 159 public void requireSelection(JComboBox comboBox, String value) { 160 String selection = requiredSelectionOf(comboBox); 161 verifyThat(selection).as(selectedIndexProperty(comboBox)).isEqualOrMatches(value); 162 } 163 164 /** 165 * Verifies that the <code>String</code> representation of the selected item in the <code>{@link JComboBox}</code> 166 * matches the given regular expression pattern. 167 * @param comboBox the target <code>JComboBox</code>. 168 * @param pattern the regular expression pattern to match. 169 * @throws AssertionError if the selected item does not match the given regular expression pattern. 170 * @throws NullPointerException if the given regular expression pattern is <code>null</code>. 171 * @see #cellReader(JComboBoxCellReader) 172 * @since 1.2 173 */ 174 @RunsInEDT 175 public void requireSelection(JComboBox comboBox, Pattern pattern) { 176 String selection = requiredSelectionOf(comboBox); 177 verifyThat(selection).as(selectedIndexProperty(comboBox)).matches(pattern); 178 } 179 180 private String requiredSelectionOf(JComboBox comboBox) throws AssertionError { 181 Object selection = selection(comboBox, cellReader); 182 if (NO_SELECTION_VALUE == selection) throw failNoSelection(comboBox); 183 return (String)selection; 184 } 185 186 /** 187 * Verifies that the index of the selected item in the <code>{@link JComboBox}</code> is equal to the given value. 188 * @param comboBox the target <code>JComboBox</code>. 189 * @param index the expected selection index. 190 * @throws AssertionError if the selection index is not equal to the given value. 191 * @since 1.2 192 */ 193 @RunsInEDT 194 public void requireSelection(JComboBox comboBox, int index) { 195 int selectedIndex = selectedIndexOf(comboBox); 196 if (selectedIndex == -1) failNoSelection(comboBox); 197 assertThat(selectedIndex).as(selectedIndexProperty(comboBox)).isEqualTo(index); 198 } 199 200 private AssertionError failNoSelection(JComboBox comboBox) { 201 throw fail(concat("[", selectedIndexProperty(comboBox).value(), "] No selection")); 202 } 203 204 /** 205 * Verifies that the <code>{@link JComboBox}</code> does not have any selection. 206 * @param comboBox the target <code>JComboBox</code>. 207 * @throws AssertionError if the <code>JComboBox</code> has a selection. 208 */ 209 @RunsInEDT 210 public void requireNoSelection(JComboBox comboBox) { 211 Object selection = selection(comboBox, cellReader); 212 if (NO_SELECTION_VALUE == selection) return; 213 fail(concat( 214 "[", selectedIndexProperty(comboBox).value(), "] Expecting no selection, but found:<", quote(selection), ">")); 215 } 216 217 /** 218 * Returns the <code>String</code> representation of the element under the given index, using this driver's 219 * <code>{@link JComboBoxCellReader}</code>. 220 * @param comboBox the target <code>JComboBox</code>. 221 * @param index the given index. 222 * @return the value of the element under the given index. 223 * @throws IndexOutOfBoundsException if the given index is negative or greater than the index of the last item in the 224 * <code>JComboBox</code>. 225 * @see #cellReader(JComboBoxCellReader) 226 */ 227 public String value(JComboBox comboBox, int index) { 228 return valueAsText(comboBox, index, cellReader); 229 } 230 231 @RunsInEDT 232 private static String valueAsText(final JComboBox comboBox, final int index, final JComboBoxCellReader cellReader) { 233 return execute(new GuiQuery<String>() { 234 protected String executeInEDT() { 235 validateIndex(comboBox, index); 236 return cellReader.valueAt(comboBox, index); 237 } 238 }); 239 } 240 241 private Description selectedIndexProperty(JComboBox comboBox) { 242 return propertyName(comboBox, SELECTED_INDEX_PROPERTY); 243 } 244 245 246 /** 247 * Clears the selection in the given <code>{@link JComboBox}</code>. Since this method does not simulate user input, 248 * it does not verifies that the <code>JComboBox</code> is enabled and showing. 249 * @param comboBox the target <code>JComboBox</code>. 250 * @since 1.2 251 */ 252 public void clearSelection(JComboBox comboBox) { 253 setSelectedIndex(comboBox, -1); 254 robot.waitForIdle(); 255 } 256 257 /** 258 * Selects the item under the given index in the <code>{@link JComboBox}</code>. 259 * @param comboBox the target <code>JComboBox</code>. 260 * @param index the given index. 261 * @throws IllegalStateException if the <code>JComboBox</code> is disabled. 262 * @throws IllegalStateException if the <code>JComboBox</code> is not showing on the screen. 263 * @throws IndexOutOfBoundsException if the given index is negative or greater than the index of the last item in the 264 * <code>JComboBox</code>. 265 */ 266 @RunsInEDT 267 public void selectItem(final JComboBox comboBox, int index) { 268 validateCanSelectItem(comboBox, index); 269 showDropDownList(comboBox); 270 selectItemAtIndex(comboBox, index); 271 hideDropDownListIfVisible(comboBox); 272 } 273 274 @RunsInEDT 275 private static void validateCanSelectItem(final JComboBox comboBox, final int index) { 276 execute(new GuiTask() { 277 protected void executeInEDT() { 278 validateIsEnabledAndShowing(comboBox); 279 validateIndex(comboBox, index); 280 } 281 }); 282 } 283 284 @VisibleForTesting 285 @RunsInEDT 286 void showDropDownList(JComboBox comboBox) { 287 // Location of pop-up button activator is LAF-dependent 288 dropDownVisibleThroughUIDelegate(comboBox, true); 289 } 290 291 @RunsInEDT 292 private void selectItemAtIndex(final JComboBox comboBox, final int index) { 293 JList dropDownList = dropDownListFinder.findDropDownList(); 294 if (dropDownList != null) { 295 listDriver.selectItem(dropDownList, index); 296 return; 297 } 298 setSelectedIndex(comboBox, index); 299 robot.waitForIdle(); 300 } 301 302 @RunsInEDT 303 private void hideDropDownListIfVisible(JComboBox comboBox) { 304 dropDownVisibleThroughUIDelegate(comboBox, false); 305 } 306 307 @RunsInEDT 308 private void dropDownVisibleThroughUIDelegate(JComboBox comboBox, final boolean visible) { 309 setPopupVisible(comboBox, visible); 310 robot.waitForIdle(); 311 } 312 313 /** 314 * Simulates a user entering the specified text in the <code>{@link JComboBox}</code>, replacing any text. This action 315 * is executed only if the <code>{@link JComboBox}</code> is editable. 316 * @param comboBox the target <code>JComboBox</code>. 317 * @param text the text to enter. 318 * @throws IllegalStateException if the <code>JComboBox</code> is disabled. 319 * @throws IllegalStateException if the <code>JComboBox</code> is not showing on the screen. 320 * @throws IllegalStateException if the <code>JComboBox</code> is not editable. 321 */ 322 @RunsInEDT 323 public void replaceText(JComboBox comboBox, String text) { 324 selectAllText(comboBox); 325 enterText(comboBox, text); 326 } 327 328 /** 329 * Simulates a user selecting the text in the <code>{@link JComboBox}</code>. This action is executed only if the 330 * <code>{@link JComboBox}</code> is editable. 331 * @param comboBox the target <code>JComboBox</code>. 332 * @throws IllegalStateException if the <code>JComboBox</code> is disabled. 333 * @throws IllegalStateException if the <code>JComboBox</code> is not showing on the screen. 334 * @throws IllegalStateException if the <code>JComboBox</code> is not editable. 335 */ 336 @RunsInEDT 337 public void selectAllText(JComboBox comboBox) { 338 Component editor = accessibleEditorOf(comboBox); 339 if (!(editor instanceof JComponent)) return; 340 focus(editor); 341 invokeAction((JComponent) editor, selectAllAction); 342 } 343 344 @RunsInEDT 345 private static Component accessibleEditorOf(final JComboBox comboBox) { 346 return execute(new GuiQuery<Component>() { 347 protected Component executeInEDT() { 348 validateEditorIsAccessible(comboBox); 349 return editorComponentOf(comboBox); 350 } 351 }); 352 } 353 354 /** 355 * Simulates a user entering the specified text in the <code>{@link JComboBox}</code>. This action is executed only 356 * if the <code>{@link JComboBox}</code> is editable. 357 * @param comboBox the target <code>JComboBox</code>. 358 * @param text the text to enter. 359 * @throws IllegalStateException if the <code>JComboBox</code> is disabled. 360 * @throws IllegalStateException if the <code>JComboBox</code> is not showing on the screen. 361 * @throws IllegalStateException if the <code>JComboBox</code> is not editable. 362 * @throws ActionFailedException if the <code>JComboBox</code> does not have an editor. 363 */ 364 @RunsInEDT 365 public void enterText(JComboBox comboBox, String text) { 366 inEdtValidateEditorIsAccessible(comboBox); 367 Component editor = editorComponentOf(comboBox); 368 // this will never happen...at least in Sun's JVM 369 if (editor == null) throw actionFailure("JComboBox does not have an editor"); 370 focusAndWaitForFocusGain(editor); 371 robot.enterText(text); 372 } 373 374 /** 375 * Simulates a user pressing and releasing the given keys on the <code>{@link JComboBox}</code>. 376 * @param comboBox the target <code>JComboBox</code>. 377 * @param keyCodes one or more codes of the keys to press. 378 * @throws NullPointerException if the given array of codes is <code>null</code>. 379 * @throws IllegalStateException if the <code>JComboBox</code> is disabled. 380 * @throws IllegalStateException if the <code>JComboBox</code> is not showing on the screen. 381 * @throws IllegalArgumentException if the given code is not a valid key code. 382 * @see java.awt.event.KeyEvent 383 */ 384 @RunsInEDT 385 public void pressAndReleaseKeys(JComboBox comboBox, int... keyCodes) { 386 if (keyCodes == null) throw new NullPointerException("The array of key codes should not be null"); 387 assertIsEnabledAndShowing(comboBox); 388 Component target = editorIfEditable(comboBox); 389 if (target == null) target = comboBox; 390 focusAndWaitForFocusGain(target); 391 robot.pressAndReleaseKeys(keyCodes); 392 } 393 394 @RunsInEDT 395 private static Component editorIfEditable(final JComboBox comboBox) { 396 return execute(new GuiQuery<Component>() { 397 protected Component executeInEDT() { 398 if (!comboBox.isEditable()) return null; 399 return editorComponent(comboBox); 400 } 401 }); 402 } 403 404 @RunsInEDT 405 private static void inEdtValidateEditorIsAccessible(final JComboBox comboBox) { 406 execute(new GuiTask() { 407 protected void executeInEDT() { 408 validateEditorIsAccessible(comboBox); 409 } 410 }); 411 } 412 413 @RunsInEDT 414 private static Component editorComponentOf(final JComboBox comboBox) { 415 return execute(new GuiQuery<Component>() { 416 protected Component executeInEDT() { 417 return editorComponent(comboBox); 418 } 419 }); 420 } 421 422 @RunsInCurrentThread 423 private static Component editorComponent(JComboBox comboBox) { 424 ComboBoxEditor editor = comboBox.getEditor(); 425 if (editor == null) return null; 426 return editor.getEditorComponent(); 427 } 428 429 /** 430 * Find the <code>{@link JList}</code> in the pop-up raised by the <code>{@link JComboBox}</code>, if the LAF actually 431 * uses one. 432 * @return the found <code>JList</code>. 433 * @throws ComponentLookupException if the <code>JList</code> in the pop-up could not be found. 434 */ 435 @RunsInEDT 436 public JList dropDownList() { 437 JList list = dropDownListFinder.findDropDownList(); 438 if (list == null) throw listNotFound(); 439 return list; 440 } 441 442 private ComponentLookupException listNotFound() { 443 throw new ComponentLookupException("Unable to find the pop-up list for the JComboBox"); 444 } 445 446 /** 447 * Asserts that the given <code>{@link JComboBox}</code> is editable. 448 * @param comboBox the target <code>JComboBox</code>. 449 * @throws AssertionError if the <code>JComboBox</code> is not editable. 450 */ 451 @RunsInEDT 452 public void requireEditable(final JComboBox comboBox) { 453 assertEditable(comboBox, true); 454 } 455 456 /** 457 * Asserts that the given <code>{@link JComboBox}</code> is not editable. 458 * @param comboBox the given <code>JComboBox</code>. 459 * @throws AssertionError if the <code>JComboBox</code> is editable. 460 */ 461 @RunsInEDT 462 public void requireNotEditable(JComboBox comboBox) { 463 assertEditable(comboBox, false); 464 } 465 466 @RunsInEDT 467 private void assertEditable(JComboBox comboBox, boolean expected) { 468 assertThat(isEditable(comboBox)).as(editableProperty(comboBox)).isEqualTo(expected); 469 } 470 471 @RunsInEDT 472 private static Description editableProperty(JComboBox comboBox) { 473 return propertyName(comboBox, EDITABLE_PROPERTY); 474 } 475 476 /** 477 * Updates the implementation of <code>{@link JComboBoxCellReader}</code> to use when comparing internal values 478 * of a <code>{@link JComboBox}</code> and the values expected in a test. 479 * @param newCellReader the new <code>JComboBoxCellValueReader</code> to use. 480 * @throws NullPointerException if <code>newCellReader</code> is <code>null</code>. 481 */ 482 public void cellReader(JComboBoxCellReader newCellReader) { 483 validateCellReader(newCellReader); 484 cellReader = newCellReader; 485 } 486 487 /** 488 * Verifies that number of items in the given <code>{@link JComboBox}</code> is equal to the expected one. 489 * @param comboBox the target <code>JComboBox</code>. 490 * @param expected the expected number of items. 491 * @throws AssertionError if the number of items in the given <code>{@link JComboBox}</code> is not equal to the 492 * expected one. 493 * @since 1.2 494 */ 495 @RunsInEDT 496 public void requireItemCount(JComboBox comboBox, int expected) { 497 int actual = itemCountIn(comboBox); 498 assertThat(actual).as(propertyName(comboBox, "itemCount")).isEqualTo(expected); 499 } 500 }