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 }