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 }