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 in compliance with
005 * 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 is distributed on
010 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011 * specific language governing permissions and limitations under the License.
012 *
013 * Copyright @2008-2010 the original author or authors.
014 */
015 package org.fest.swing.driver;
016
017 import static java.awt.event.KeyEvent.VK_UNDEFINED;
018 import static org.fest.swing.driver.Actions.findActionKey;
019 import static org.fest.swing.driver.JComponentToolTipQuery.toolTipOf;
020 import static org.fest.swing.driver.KeyStrokes.findKeyStrokesForAction;
021 import static org.fest.swing.driver.TextAssert.verifyThat;
022 import static org.fest.swing.edt.GuiActionRunner.execute;
023 import static org.fest.swing.exception.ActionFailedException.actionFailure;
024 import static org.fest.util.Strings.concat;
025 import static org.fest.util.Strings.quote;
026
027 import java.awt.Point;
028 import java.awt.Rectangle;
029 import java.util.regex.Pattern;
030
031 import javax.swing.JComponent;
032 import javax.swing.KeyStroke;
033
034 import org.fest.swing.annotation.RunsInCurrentThread;
035 import org.fest.swing.annotation.RunsInEDT;
036 import org.fest.swing.core.Robot;
037 import org.fest.swing.edt.GuiQuery;
038 import org.fest.swing.exception.ActionFailedException;
039
040 /**
041 * Understands functional testing of <code>{@link JComponent}</code>s:
042 * <ul>
043 * <li>user input simulation</li>
044 * <li>state verification</li>
045 * <li>property value query</li>
046 * </ul>
047 * This class is intended for internal use only. Please use the classes in the package
048 * <code>{@link org.fest.swing.fixture}</code> in your tests.
049 *
050 * @author Alex Ruiz
051 * @author Yvonne Wang
052 */
053 public class JComponentDriver extends ContainerDriver {
054
055 private static final String TOOL_TIP_TEXT_PROPERTY = "toolTipText";
056
057 /**
058 * Creates a new </code>{@link JComponentDriver}</code>.
059 * @param robot the robot the robot to use to simulate user input.
060 */
061 public JComponentDriver(Robot robot) {
062 super(robot);
063 }
064
065 /**
066 * Invoke <code>{@link JComponent#scrollRectToVisible(Rectangle)}</code> on the given <code>{@link JComponent}</code>.
067 * <p>
068 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
069 * responsible for calling this method from the EDT.
070 * </p>
071 * @param c the given <code>JComponent</code>.
072 * @param r the visible <code>Rectangle</code>.
073 */
074 @RunsInCurrentThread
075 protected final void scrollToVisible(JComponent c, Rectangle r) {
076 // From Abbot:
077 // Ideally, we'd use scrollBar commands to effect the scrolling, but that gets really complicated for no real gain
078 // in function. Fortunately, Swing's Scrollable makes for a simple solution.
079 // NOTE: absolutely MUST wait for idle in order for the scroll to finish, and the UI to update so that the next
080 // action goes to the proper location within the scrolled component.
081 c.scrollRectToVisible(r);
082 }
083
084 /**
085 * Indicates whether the given <code>{@link JComponent}</code>'s visible <code>{@link Rectangle}</code> contains the
086 * given one.
087 * <p>
088 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
089 * responsible for calling this method from the EDT.
090 * </p>
091 * @param c the given <code>JComponent</code>.
092 * @param r the <code>Rectangle</code> to verify.
093 * @return <code>true</code> if the given <code>Rectangle</code> is contained in the given <code>JComponent</code>'s
094 * visible <code>Rectangle</code>.
095 */
096 @RunsInCurrentThread
097 protected static boolean isVisible(JComponent c, Rectangle r) {
098 return c.getVisibleRect().contains(r);
099 }
100
101 /**
102 * Indicates whether the given <code>{@link JComponent}</code>'s visible <code>{@link Rectangle}</code> contains
103 * the given <code>{@link Point}</code>.
104 * <p>
105 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
106 * responsible for calling this method from the EDT.
107 * </p>
108 * @param c the given <code>JComponent</code>.
109 * @param p the <code>Point</code> to verify.
110 * @return <code>true</code> if the given <code>Point</code> is contained in the given <code>JComponent</code>'s
111 * visible <code>Rectangle</code>.
112 */
113 @RunsInCurrentThread
114 protected final boolean isVisible(JComponent c, Point p) {
115 return c.getVisibleRect().contains(p);
116 }
117
118 /**
119 * Invoke an <code>{@link javax.swing.Action}</code> from the <code>{@link JComponent}</code>'s
120 * <code>{@link javax.swing.ActionMap}</code>.
121 * @param c the given <code>JComponent</code>.
122 * @param name the name of the <code>Action</code> to invoke.
123 * @throws ActionFailedException if an <code>Action</code> cannot be found under the given name.
124 * @throws ActionFailedException if a <code>KeyStroke</code> cannot be found for the <code>Action</code> under the
125 * given name.
126 * @throws ActionFailedException if it is not possible to type any of the found <code>KeyStroke</code>s.
127 */
128 @RunsInEDT
129 protected final void invokeAction(JComponent c, String name) {
130 robot.focusAndWaitForFocusGain(c);
131 for (KeyStroke keyStroke : keyStrokesForAction(c, name)) {
132 try {
133 type(keyStroke);
134 robot.waitForIdle();
135 return;
136 } catch (IllegalArgumentException e) { /* try the next one, if any */ }
137 }
138 throw actionFailure(concat("Unable to type any key for the action with key ", quote(name)));
139 }
140
141 @RunsInCurrentThread
142 private static KeyStroke[] keyStrokesForAction(JComponent component, String actionName) {
143 Object key = findActionKey(actionName, component.getActionMap());
144 return findKeyStrokesForAction(actionName, key, component.getInputMap());
145 }
146
147 private void type(KeyStroke keyStroke) {
148 if (keyStroke.getKeyCode() == VK_UNDEFINED) {
149 robot.type(keyStroke.getKeyChar());
150 return;
151 }
152 robot.pressAndReleaseKey(keyStroke.getKeyCode(), keyStroke.getModifiers());
153 }
154
155 /**
156 * Asserts that the toolTip in the given <code>{@link JComponent}</code> matches the given value.
157 * @param c the given <code>JComponent</code>.
158 * @param expected the expected toolTip. It can be a regular expression.
159 * @throws AssertionError if the toolTip of the given <code>JComponent</code> does not match the given value.
160 * @since 1.2
161 */
162 @RunsInEDT
163 public void requireToolTip(JComponent c, String expected) {
164 verifyThat(toolTipOf(c)).as(propertyName(c, TOOL_TIP_TEXT_PROPERTY)).isEqualOrMatches(expected);
165 }
166
167 /**
168 * Asserts that the toolTip in the given <code>{@link JComponent}</code> matches the given regular expression pattern.
169 * @param c the given <code>JComponent</code>.
170 * @param pattern the regular expression pattern to match.
171 * @throws NullPointerException if the given regular expression pattern is <code>null</code>.
172 * @throws AssertionError if the toolTip of the given <code>JComponent</code> does not match the given value.
173 * @since 1.2
174 */
175 @RunsInEDT
176 public void requireToolTip(JComponent c, Pattern pattern) {
177 verifyThat(toolTipOf(c)).as(propertyName(c, TOOL_TIP_TEXT_PROPERTY)).matches(pattern);
178 }
179
180 /**
181 * Returns the client property stored in the given <code>{@link JComponent}</code>, under the given key.
182 * @param c the given <code>JComponent</code>.
183 * @param key the key to use to retrieve the client property.
184 * @return the value of the client property stored under the given key, or <code>null</code> if the property was
185 * not found.
186 * @throws NullPointerException if the given key is <code>null</code>.
187 * @since 1.2
188 */
189 @RunsInEDT
190 public Object clientProperty(JComponent c, Object key) {
191 if (key == null) throw new NullPointerException("The key of the client property to return should not be null");
192 return clientPropertyIn(c, key);
193 }
194
195 private static Object clientPropertyIn(final JComponent c, final Object key) {
196 return execute(new GuiQuery<Object>() {
197 protected Object executeInEDT() {
198 return c.getClientProperty(key);
199 }
200 });
201 }
202 }