001 /* 002 * Created on Jan 26, 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.swing.driver.ComponentStateValidator.validateIsEnabledAndShowing; 021 import static org.fest.swing.driver.JSpinnerSetValueTask.setValue; 022 import static org.fest.swing.driver.JSpinnerValueQuery.valueOf; 023 import static org.fest.swing.edt.GuiActionRunner.execute; 024 import static org.fest.swing.exception.ActionFailedException.actionFailure; 025 import static org.fest.swing.format.Formatting.format; 026 import static org.fest.util.Strings.concat; 027 import static org.fest.util.Strings.quote; 028 029 import java.awt.Component; 030 import java.text.ParseException; 031 import java.util.ArrayList; 032 import java.util.List; 033 034 import javax.swing.JSpinner; 035 import javax.swing.text.JTextComponent; 036 037 import org.fest.swing.annotation.RunsInCurrentThread; 038 import org.fest.swing.annotation.RunsInEDT; 039 import org.fest.swing.core.Robot; 040 import org.fest.swing.core.TypeMatcher; 041 import org.fest.swing.edt.GuiTask; 042 import org.fest.swing.exception.*; 043 044 /** 045 * Understands functional testing of <code>{@link JSpinner}</code>s: 046 * <ul> 047 * <li>user input simulation</li> 048 * <li>state verification</li> 049 * <li>property value query</li> 050 * </ul> 051 * This class is intended for internal use only. Please use the classes in the package 052 * <code>{@link org.fest.swing.fixture}</code> in your tests. 053 * 054 * @author Alex Ruiz 055 * @author Yvonne Wang 056 */ 057 public class JSpinnerDriver extends JComponentDriver { 058 059 private static final TypeMatcher EDITOR_MATCHER = new TypeMatcher(JTextComponent.class, true); 060 private static final String VALUE_PROPERTY = "value"; 061 062 /** 063 * Creates a new </code>{@link JSpinnerDriver}</code>. 064 * @param robot the robot to use to simulate user input. 065 */ 066 public JSpinnerDriver(Robot robot) { 067 super(robot); 068 } 069 070 /** 071 * Increments the value of the <code>{@link JSpinner}</code> the given number of times. 072 * @param spinner the target <code>JSpinner</code>. 073 * @param times how many times the value of this fixture's <code>JSpinner</code> should be incremented. 074 * @throws IllegalArgumentException if <code>times</code> is less than or equal to zero. 075 * @throws IllegalStateException if the <code>JSpinner</code> is disabled. 076 * @throws IllegalStateException if the <code>JSpinner</code> is not showing on the screen. 077 */ 078 @RunsInEDT 079 public void increment(JSpinner spinner, int times) { 080 validate(times, "increment the value"); 081 validateAndIncrementValue(spinner, times); 082 robot.waitForIdle(); 083 } 084 085 @RunsInEDT 086 private static void validateAndIncrementValue(final JSpinner spinner, final int times) { 087 execute(new GuiTask() { 088 protected void executeInEDT() { 089 validateIsEnabledAndShowing(spinner); 090 incrementValue(spinner, times); 091 } 092 }); 093 } 094 095 @RunsInCurrentThread 096 private static void incrementValue(JSpinner spinner, int times) { 097 for (int i = 0; i < times; i++) { 098 Object newValue = spinner.getNextValue(); 099 if (newValue == null) return; 100 spinner.setValue(newValue); 101 } 102 } 103 104 /** 105 * Increments the value of the <code>{@link JSpinner}</code>. 106 * @param spinner the target <code>JSpinner</code>. 107 * @throws IllegalStateException if the <code>JSpinner</code> is disabled. 108 * @throws IllegalStateException if the <code>JSpinner</code> is not showing on the screen. 109 */ 110 @RunsInEDT 111 public void increment(JSpinner spinner) { 112 validateAndIncrementValue(spinner); 113 robot.waitForIdle(); 114 } 115 116 @RunsInEDT 117 private static void validateAndIncrementValue(final JSpinner spinner) { 118 execute(new GuiTask() { 119 protected void executeInEDT() { 120 validateIsEnabledAndShowing(spinner); 121 Object newValue = spinner.getNextValue(); 122 if (newValue != null) spinner.setValue(newValue); 123 } 124 }); 125 } 126 127 /** 128 * Decrements the value of the <code>{@link JSpinner}</code> the given number of times. 129 * @param spinner the target <code>JSpinner</code>. 130 * @param times how many times the value of this fixture's <code>JSpinner</code> should be decremented. 131 * @throws IllegalArgumentException if <code>times</code> is less than or equal to zero. 132 * @throws IllegalStateException if the <code>JSpinner</code> is disabled. 133 * @throws IllegalStateException if the <code>JSpinner</code> is not showing on the screen. 134 */ 135 @RunsInEDT 136 public void decrement(JSpinner spinner, int times) { 137 validate(times, "decrement the value"); 138 validateAndDecrementValue(spinner, times); 139 robot.waitForIdle(); 140 } 141 142 private void validate(int times, String action) { 143 if (times > 0) return; 144 throw new IllegalArgumentException(concat( 145 "The number of times to ", action, " should be greater than zero, but was <", times, ">")); 146 } 147 148 @RunsInEDT 149 private static void validateAndDecrementValue(final JSpinner spinner, final int times) { 150 execute(new GuiTask() { 151 protected void executeInEDT() { 152 validateIsEnabledAndShowing(spinner); 153 decrementValue(spinner, times); 154 } 155 }); 156 } 157 158 @RunsInCurrentThread 159 private static void decrementValue(JSpinner spinner, int times) { 160 for (int i = 0; i < times; i++) { 161 Object newValue = spinner.getPreviousValue(); 162 if (newValue == null) return; 163 spinner.setValue(newValue); 164 } 165 } 166 167 /** 168 * Decrements the value of the <code>{@link JSpinner}</code>. 169 * @param spinner the target <code>JSpinner</code>. 170 * @throws IllegalStateException if the <code>JSpinner</code> is disabled. 171 * @throws IllegalStateException if the <code>JSpinner</code> is not showing on the screen. 172 */ 173 @RunsInEDT 174 public void decrement(JSpinner spinner) { 175 validateAndDecrementValue(spinner); 176 robot.waitForIdle(); 177 } 178 179 @RunsInEDT 180 private static void validateAndDecrementValue(final JSpinner spinner) { 181 execute(new GuiTask() { 182 protected void executeInEDT() { 183 validateIsEnabledAndShowing(spinner); 184 Object newValue = spinner.getPreviousValue(); 185 if (newValue != null) spinner.setValue(newValue); 186 } 187 }); 188 } 189 190 /** 191 * Returns the text displayed in the given <code>{@link JSpinner}</code>. This method first tries to get the text 192 * displayed in the <code>JSpinner</code>'s editor, assuming it is a <code>{@link JTextComponent}</code>. If the 193 * text from the editor cannot be retrieved, it will return the <code>String</code> representation of the value 194 * in the <code>JSpinner</code>'s model. 195 * @param spinner the target <code>JSpinner</code>. 196 * @return the text displayed in the given <code>JSpinner</code>. 197 * @since 1.2 198 */ 199 @RunsInEDT 200 public String textOf(JSpinner spinner) { 201 JTextComponent editor = findEditor(spinner); 202 if (editor != null) return JTextComponentTextQuery.textOf(editor); 203 Object value = valueOf(spinner); 204 return value != null ? value.toString() : null; 205 } 206 207 /** 208 * Enters and commits the given text in the <code>{@link JSpinner}</code>, assuming its editor has a 209 * <code>{@link JTextComponent}</code> under it. 210 * @param spinner the target <code>JSpinner</code>. 211 * @param text the text to enter. 212 * @throws IllegalStateException if the <code>JSpinner</code> is disabled. 213 * @throws IllegalStateException if the <code>JSpinner</code> is not showing on the screen. 214 * @throws ActionFailedException if the editor of the <code>JSpinner</code> is not a <code>JTextComponent</code> or 215 * cannot be found. 216 * @throws UnexpectedException if entering the text in the <code>JSpinner</code>'s editor fails. 217 */ 218 @RunsInEDT 219 public void enterTextAndCommit(JSpinner spinner, String text) { 220 enterText(spinner, text); 221 commit(spinner); 222 robot.waitForIdle(); 223 } 224 225 @RunsInEDT 226 private static void commit(final JSpinner spinner) { 227 execute(new GuiTask() { 228 protected void executeInEDT() throws ParseException { 229 spinner.commitEdit(); 230 } 231 }); 232 } 233 234 /** 235 * Enters the given text in the <code>{@link JSpinner}</code>, assuming its editor has a 236 * <code>{@link JTextComponent}</code> under it. This method does not commit the value to the <code>JSpinner</code>. 237 * @param spinner the target <code>JSpinner</code>. 238 * @param text the text to enter. 239 * @throws IllegalStateException if the <code>JSpinner</code> is disabled. 240 * @throws IllegalStateException if the <code>JSpinner</code> is not showing on the screen. 241 * @throws ActionFailedException if the editor of the <code>JSpinner</code> is not a <code>JTextComponent</code> or 242 * cannot be found. 243 * @throws UnexpectedException if entering the text in the <code>JSpinner</code>'s editor fails. 244 * @see #enterTextAndCommit(JSpinner, String) 245 */ 246 @RunsInEDT 247 public void enterText(JSpinner spinner, String text) { 248 assertIsEnabledAndShowing(spinner); 249 JTextComponent editor = findEditor(spinner); 250 validate(spinner, editor); 251 robot.waitForIdle(); 252 robot.focusAndWaitForFocusGain(editor); 253 invokeAction(editor, selectAllAction); 254 robot.enterText(text); 255 } 256 257 @RunsInEDT 258 private JTextComponent findEditor(JSpinner spinner) { 259 List<Component> found = new ArrayList<Component>(robot.finder().findAll(spinner, EDITOR_MATCHER)); 260 if (found.size() != 1) return null; 261 Component c = found.get(0); 262 if (c instanceof JTextComponent) return (JTextComponent)c; 263 return null; 264 } 265 266 @RunsInEDT 267 private static void validate(final JSpinner spinner, final JTextComponent editor) { 268 execute(new GuiTask() { 269 protected void executeInEDT() { 270 if (editor == null) throw actionFailure(concat("Unable to find editor for ", format(spinner))); 271 } 272 }); 273 } 274 275 /** 276 * Selects the given value in the given <code>{@link JSpinner}</code>. 277 * @param spinner the target <code>JSpinner</code>. 278 * @param value the value to select. 279 * @throws IllegalStateException if the <code>JSpinner</code> is disabled. 280 * @throws IllegalStateException if the <code>JSpinner</code> is not showing on the screen. 281 * @throws IllegalArgumentException if the given <code>JSpinner</code> does not support the given value. 282 */ 283 @RunsInEDT 284 public void selectValue(JSpinner spinner, Object value) { 285 try { 286 setValue(spinner, value); 287 } catch (IllegalArgumentException e) { 288 // message from original exception is useless 289 throw new IllegalArgumentException(concat("Value ", quote(value), " is not valid")); 290 } 291 robot.waitForIdle(); 292 } 293 294 /** 295 * Returns the <code>{@link JTextComponent}</code> used as editor in the given <code>{@link JSpinner}</code>. 296 * @param spinner the target <code>JSpinner</code>. 297 * @return the <code>JTextComponent</code> used as editor in the given <code>JSpinner</code>. 298 * @throws ComponentLookupException if the given <code>JSpinner</code> does not have a <code>JTextComponent</code> as 299 * editor. 300 */ 301 @RunsInEDT 302 public JTextComponent editor(JSpinner spinner) { 303 return (JTextComponent)robot.finder().find(spinner, EDITOR_MATCHER); 304 } 305 306 /** 307 * Verifies that the value of the <code>{@link JSpinner}</code> is equal to the given one. 308 * @param spinner the target <code>JSpinner</code>. 309 * @param value the expected value. 310 * @throws AssertionError if the value of the <code>JSpinner</code> is not equal to the given one. 311 */ 312 @RunsInEDT 313 public void requireValue(JSpinner spinner, Object value) { 314 assertThat(valueOf(spinner)).as(propertyName(spinner, VALUE_PROPERTY)).isEqualTo(value); 315 } 316 }