/** * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * * Copyright 2012-2015 the original author or authors. */ package org.assertj.swing.fixture; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.util.Preconditions.checkNotNull; import static org.assertj.swing.driver.ComponentDriver.propertyName; import static org.assertj.swing.format.Formatting.format; import java.awt.Color; import java.awt.Component; import java.awt.Font; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.assertj.swing.core.KeyPressInfo; import org.assertj.swing.core.MouseButton; import org.assertj.swing.core.MouseClickInfo; import org.assertj.swing.core.Robot; import org.assertj.swing.driver.ComponentDriver; import org.assertj.swing.query.ComponentEnabledQuery; import org.assertj.swing.timing.Timeout; /** * Supports functional testing of AWT and Swing {@code Component}s. * * @param <S> used to simulate "self types." For more information please read "<a href="http://goo.gl/fjgOM" * target="_blank">Emulating 'self types' using Java Generics to simplify fluent API implementation</a>." * @param <C> the type of {@code Component} that this fixture can manage. * @param <D> the type of {@link ComponentDriver} that this fixture uses internally. * * @author Alex Ruiz * @author Yvonne Wang */ public abstract class AbstractComponentFixture<S, C extends Component, D extends ComponentDriver> implements MouseInputSimulationFixture<S> { /** Name of the property "font". */ protected static final String FONT_PROPERTY = "font"; /** Name of the property "background". */ protected static final String BACKGROUND_PROPERTY = "background"; /** Name of the property "foreground". */ protected static final String FOREGROUND_PROPERTY = "foreground"; /** Performs simulation of user events on {@link #target} */ private final Robot robot; private final C target; private final S myself; private D driver; /** * Creates a new {@link AbstractComponentFixture}. * * @param selfType the "self type." * @param robot performs simulation of user events on a {@code Component}. * @param type the type of the {@code Component} to find using the given {@code Robot}. * @throws NullPointerException if {@code robot} is {@code null}. * @throws NullPointerException if {@code type} is {@code null}. * @throws org.assertj.swing.exception.ComponentLookupException if a matching component could not be found. * @throws org.assertj.swing.exception.ComponentLookupException if more than one matching component is found. */ public AbstractComponentFixture(@Nonnull Class<S> selfType, @Nonnull Robot robot, @Nonnull Class<? extends C> type) { this(selfType, robot, findTarget(robot, type)); } private static @Nonnull <C extends Component> C findTarget(@Nonnull Robot robot, @Nonnull Class<? extends C> type) { checkNotNull(robot); checkNotNull(type); return robot.finder().findByType(type, requireShowing(robot)); } /** * Creates a new {@link AbstractComponentFixture}. * * @param selfType the "self type." * @param robot performs simulation of user events on a {@code Component}. * @param name the name of the {@code Component} to find using the given {@code Robot}. * @param type the type of the {@code Component} to find using the given {@code Robot}. * @throws NullPointerException if {@code robot} is {@code null}. * @throws NullPointerException if {@code type} is {@code null}. * @throws org.assertj.swing.exception.ComponentLookupException if a matching component could not be found. * @throws org.assertj.swing.exception.ComponentLookupException if more than one matching component is found. */ public AbstractComponentFixture(@Nonnull Class<S> selfType, @Nonnull Robot robot, @Nullable String name, @Nonnull Class<? extends C> type) { this(selfType, robot, findTarget(robot, name, type)); } private static @Nonnull <C extends Component> C findTarget(@Nonnull Robot robot, @Nullable String name, @Nonnull Class<? extends C> type) { checkNotNull(robot); checkNotNull(type); return robot.finder().findByName(name, type, requireShowing(robot)); } /** * Creates a new {@link AbstractComponentFixture}. * * @param selfType the "self type." * @param robot performs simulation of user events on the given {@code Component}. * @param target the {@code Component} to be managed by this fixture. * @throws NullPointerException if {@code selfType} is {@code null}. * @throws NullPointerException if {@code robot} is {@code null}. * @throws NullPointerException if {@code target} is {@code null}. */ public AbstractComponentFixture(@Nonnull Class<S> selfType, @Nonnull Robot robot, @Nonnull C target) { myself = checkNotNull(selfType).cast(this); this.robot = checkNotNull(robot); this.target = checkNotNull(target); replaceDriverWith(createDriver(robot)); } protected abstract @Nonnull D createDriver(@Nonnull Robot robot); protected final @Nonnull D driver() { return driver; } public final void replaceDriverWith(@Nonnull D driver) { this.driver = checkNotNull(driver); } /** * Simulates a user clicking this fixture's {@code Component}. * * @return this fixture. * @throws IllegalStateException if {@link Settings#clickOnDisabledComponentsAllowed()} is <code>false</code> and this * fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. */ @Override public final @Nonnull S click() { driver.click(target()); return myself(); } /** * Simulates a user clicking this fixture's {@code Component}. * * @param button the button to click. * @return this fixture. * @throws NullPointerException if the given {@code MouseButton} is {@code null}. * @throws IllegalStateException if {@link Settings#clickOnDisabledComponentsAllowed()} is <code>false</code> and this * fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. */ @Override public final @Nonnull S click(@Nonnull MouseButton button) { driver.click(target(), button); return myself(); } /** * Simulates a user clicking this fixture's {@code Component}. * * @param mouseClickInfo specifies the button to click and the times the button should be clicked. * @return this fixture. * @throws NullPointerException if the given {@code MouseClickInfo} is {@code null}. * @throws IllegalStateException if {@link Settings#clickOnDisabledComponentsAllowed()} is <code>false</code> and this * fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. */ @Override public final @Nonnull S click(@Nonnull MouseClickInfo mouseClickInfo) { driver.click(target(), mouseClickInfo); return myself(); } /** * Simulates a user double-clicking this fixture's {@code Component}. * * @return this fixture. * @throws IllegalStateException if {@link Settings#clickOnDisabledComponentsAllowed()} is <code>false</code> and this * fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. */ @Override public final @Nonnull S doubleClick() { driver.doubleClick(target()); return myself(); } /** * Simulates a user right-clicking this fixture's {@code Component}. * * @return this fixture. * @throws IllegalStateException if {@link Settings#clickOnDisabledComponentsAllowed()} is <code>false</code> and this * fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. */ @Override public final @Nonnull S rightClick() { driver.rightClick(target()); return myself(); } /** * Gives input focus to this fixture's {@code Component}. * * @return this fixture. * @throws IllegalStateException if this fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. */ public final @Nonnull S focus() { driver.focus(target()); return myself(); } /** * Simulates a user pressing given key with the given modifiers on this fixture's {@code Component}. Modifiers is a * mask from the available AWT {@code InputEvent} masks. * * @param keyPressInfo specifies the key and modifiers to press. * @return this fixture. * @throws NullPointerException if the given {@code KeyPressInfo} is {@code null}. * @throws IllegalArgumentException if the given code is not a valid key code. * @throws IllegalStateException if this fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. * @see KeyPressInfo */ public final @Nonnull S pressAndReleaseKey(@Nonnull KeyPressInfo keyPressInfo) { driver.pressAndReleaseKey(target(), keyPressInfo); return myself(); } /** * Simulates a user pressing and releasing the given keys on this fixture's {@code Component}. * * @param keyCodes one or more codes of the keys to press. * @return this fixture. * @throws NullPointerException if the given array of codes is {@code null}. * @throws IllegalArgumentException if any of the given code is not a valid key code. * @throws IllegalStateException if this fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. * @see java.awt.event.KeyEvent */ public final @Nonnull S pressAndReleaseKeys(@Nonnull int... keyCodes) { driver.pressAndReleaseKeys(target(), keyCodes); return myself(); } /** * Simulates a user pressing the given key on this fixture's {@code Component}. * * @param keyCode the code of the key to press. * @return this fixture. * @throws IllegalArgumentException if any of the given code is not a valid key code. * @throws IllegalStateException if this fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. * @see #pressKeyWhileRunning(int, Runnable) * @see java.awt.event.KeyEvent */ public final @Nonnull S pressKey(int keyCode) { driver.pressKey(target(), keyCode); return myself(); } /** * Simulates a user pressing the given key on this fixture's {@code Component}, running the given runnable and * releasing the key again. * * @param keyCode the code of the key to press. * @return this fixture. * @throws IllegalArgumentException if any of the given code is not a valid key code. * @throws IllegalStateException if this fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. * @see #pressKey(int) * @see java.awt.event.KeyEvent */ public final @Nonnull S pressKeyWhileRunning(int keyCode, @Nonnull Runnable runnable) { driver.pressKeyWhileRunning(target(), keyCode, runnable); return myself(); } /** * Simulates a user releasing the given key on this fixture's {@code Component}. * * @param keyCode the code of the key to release. * @return this fixture. * @throws IllegalArgumentException if any of the given code is not a valid key code. * @throws IllegalStateException if this fixture's {@code Component} is disabled. * @throws IllegalStateException if this fixture's {@code Component} is not showing on the screen. * @see java.awt.event.KeyEvent */ public final @Nonnull S releaseKey(int keyCode) { driver.releaseKey(target(), keyCode); return myself(); } /** * Asserts that this fixture's {@code Component} has input focus. * * @return this fixture. * @throws AssertionError if this fixture's {@code Component} does not have input focus. */ public final @Nonnull S requireFocused() { driver.requireFocused(target()); return myself(); } /** * Asserts that this fixture's {@code Component} is enabled. * * @return this fixture. * @throws AssertionError if this fixture's {@code Component} is disabled. */ public final @Nonnull S requireEnabled() { driver.requireEnabled(target()); return myself(); } /** * Asserts that this fixture's {@code Component} is enabled. * * @param timeout the time this fixture will wait for the component to be enabled. * @return this fixture. * @throws org.assertj.swing.exception.WaitTimedOutError if this fixture's {@code Component} is never enabled. */ public final @Nonnull S requireEnabled(@Nonnull Timeout timeout) { driver.requireEnabled(target(), timeout); return myself(); } /** * Asserts that this fixture's {@code Component} is disabled. * * @return this fixture. * @throws AssertionError if this fixture's {@code Component} is enabled. */ public final @Nonnull S requireDisabled() { driver.requireDisabled(target()); return myself(); } /** * Asserts that this fixture's {@code Component} is visible. * * @return this fixture. * @throws AssertionError if this fixture's {@code Component} is not visible. */ public final @Nonnull S requireVisible() { driver.requireVisible(target()); return myself(); } /** * Asserts that this fixture's {@code Component} is not visible. * * @return this fixture. * @throws AssertionError if this fixture's {@code Component} is visible. */ public final @Nonnull S requireNotVisible() { driver.requireNotVisible(target()); return myself(); } /** * Returns whether showing components are the only ones participating in a component lookup. The returned value is * obtained from the {@link org.assertj.swing.core.Settings#componentLookupScope() component lookup scope} stored in * this fixture's {@link Robot}. * * @return {@code true} if only showing components can participate in a component lookup, {@code false} otherwise. */ protected boolean requireShowing() { return requireShowing(robot()); } private static boolean requireShowing(@Nonnull Robot robot) { return robot.settings().componentLookupScope().requireShowing(); } /** * @return a fixture that checks properties of the font of this fixture's {@code Component}. */ public final @Nonnull FontFixture font() { Font font = driver.fontOf(target); return new FontFixture(font, propertyName(target(), FONT_PROPERTY)); } /** * @return a fixture that checks properties of the background color of this fixture's {@code Component}. */ public final @Nonnull ColorFixture background() { Color background = driver.backgroundOf(target); return new ColorFixture(background, propertyName(target(), BACKGROUND_PROPERTY)); } /** * @return a fixture that checks properties of the foreground color of this fixture's {@code Component}. */ public final @Nonnull ColorFixture foreground() { Color foreground = driver.foregroundOf(target); return new ColorFixture(foreground, propertyName(target(), FOREGROUND_PROPERTY)); } /** * Indicates whether this fixture's {@code Component} is enabled. * * @return {@code true} if this fixture's {@code Component} is enabled; {@code false} otherwise. */ public final boolean isEnabled() { return ComponentEnabledQuery.isEnabled(target()); } /** * Returns this fixture's {@code Component} casted to the given sub-type. * * @param <T> the type that the managed {@code Component} will be casted to. * @param type the class for the type of component. * @return this fixture's {@code Component} casted to the given sub-type. * @throws AssertionError if this fixture's {@code Component} is not an instance of the given type. */ public final @Nonnull <T extends C> T targetCastedTo(@Nonnull Class<T> type) { assertThat(target).as(format(target)).isInstanceOf(type); return type.cast(target); } /** * <p> * Returns the GUI component in this fixture. * </p> * <p> * <strong>Note:</strong> Access to the GUI component returned by this method <em>must</em> be executed in the event * dispatch thread (EDT). To do so, please execute a {@link org.assertj.swing.edt.GuiQuery GuiQuery} or * {@link org.assertj.swing.edt.GuiTask GuiTask} (depending on what you need to do), inside a * {@link org.assertj.swing.edt.GuiActionRunner}. To learn more about Swing threading, please read the <a * href="http://java.sun.com/javase/6/docs/api/javax/swing/package-summary.html#threading" target="_blank">Swing * Threading Policy</a>. * </p> * * @return the GUI component in this fixture. */ public final @Nonnull C target() { return target; } /** @return the {@link Robot} that simulates user events on {@link #target()}. */ protected final @Nonnull Robot robot() { return robot; } /** * @return {@code this} casted to the "self type". */ protected final @Nonnull S myself() { return myself; } }