All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.assertj.swing.driver.JTextComponentDriver Maven / Gradle / Ivy

There is a newer version: 3.17.1
Show newest version
/*
 * Created on Jan 21, 2008
 * 
 * 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 @2008-2013 the original author or authors.
 */
package org.assertj.swing.driver;

import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.String.valueOf;
import static javax.swing.text.DefaultEditorKit.deletePrevCharAction;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.util.Preconditions.checkNotNull;
import static org.assertj.core.util.Preconditions.checkNotNullOrEmpty;
import static org.assertj.core.util.Strings.concat;
import static org.assertj.core.util.Strings.isNullOrEmpty;
import static org.assertj.core.util.Strings.quote;
import static org.assertj.swing.driver.ComponentPreconditions.checkEnabledAndShowing;
import static org.assertj.swing.driver.JTextComponentEditableQuery.isEditable;
import static org.assertj.swing.driver.JTextComponentSelectAllTask.selectAllText;
import static org.assertj.swing.driver.JTextComponentSelectTextTask.selectTextInRange;
import static org.assertj.swing.driver.JTextComponentSetTextTask.setTextIn;
import static org.assertj.swing.driver.PointAndParentForScrollingJTextFieldQuery.pointAndParentForScrolling;
import static org.assertj.swing.driver.TextAssert.verifyThat;
import static org.assertj.swing.edt.GuiActionRunner.execute;
import static org.assertj.swing.exception.ActionFailedException.actionFailure;
import static org.assertj.swing.format.Formatting.format;

import java.awt.Container;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.regex.Pattern;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.CellRendererPane;
import javax.swing.JComponent;
import javax.swing.JTextField;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;

import org.assertj.core.description.Description;
import org.assertj.swing.annotation.RunsInCurrentThread;
import org.assertj.swing.annotation.RunsInEDT;
import org.assertj.swing.core.Robot;
import org.assertj.swing.edt.GuiQuery;
import org.assertj.swing.edt.GuiTask;
import org.assertj.swing.exception.ActionFailedException;
import org.assertj.swing.internal.annotation.InternalApi;
import org.assertj.swing.util.Pair;
import org.assertj.swing.util.Platform;

/**
 * 

* Supports functional testing of {@code JTextComponent}s. *

* *

* Note: This class is intended for internal use only. Please use the classes in the package * {@link org.assertj.swing.fixture} in your tests. *

* * @author Alex Ruiz */ @InternalApi public class JTextComponentDriver extends JComponentDriver implements TextDisplayDriver { private static final String EDITABLE_PROPERTY = "editable"; private static final String TEXT_PROPERTY = "text"; /** * Creates a new {@link JTextComponentDriver}. * * @param robot the robot to use to simulate user input. */ public JTextComponentDriver(@Nonnull Robot robot) { super(robot); } /** * Deletes the text of the {@code JTextComponent}. * * @param textBox the target {@code JTextComponent}. * @throws IllegalStateException if the {@code JTextComponent} is disabled. * @throws IllegalStateException if the {@code JTextComponent} is not showing on the screen. */ @RunsInEDT public void deleteText(@Nonnull JTextComponent textBox) { selectAll(textBox); invokeAction(textBox, deletePrevCharAction); } /** * Types the given text into the {@code JTextComponent}, replacing any existing text already there. * * @param textBox the target {@code JTextComponent}. * @param text the text to enter. * @throws NullPointerException if the text to enter is {@code null}. * @throws IllegalArgumentException if the text to enter is empty. * @throws IllegalStateException if the {@code JTextComponent} is disabled. * @throws IllegalStateException if the {@code JTextComponent} is not showing on the screen. */ @RunsInEDT public void replaceText(@Nonnull JTextComponent textBox, @Nonnull String text) { checkNotNullOrEmpty(text); selectAll(textBox); enterText(textBox, text); } /** * Selects the text in the {@code JTextComponent}. * * @param textBox the target {@code JTextComponent}. * @throws IllegalStateException if the {@code JTextComponent} is disabled. * @throws IllegalStateException if the {@code JTextComponent} is not showing on the screen. */ @RunsInEDT public void selectAll(@Nonnull JTextComponent textBox) { checkStateAndScrollToPosition(textBox, 0); selectAllText(textBox); } /** * Types the given text into the {@code JTextComponent}. * * @param textBox the target {@code JTextComponent}. * @param text the text to enter. * @throws IllegalStateException if the {@code JTextComponent} is disabled. * @throws IllegalStateException if the {@code JTextComponent} is not showing on the screen. */ @RunsInEDT public void enterText(@Nonnull JTextComponent textBox, @Nonnull String text) { focusAndWaitForFocusGain(textBox); robot.enterText(text); } /** *

* Sets the given text into the {@code JTextComponent}. Unlike {@link #enterText(JTextComponent, String)}, this method * bypasses the event system and allows immediate updating on the underlying document model. *

*

* Primarily desired for speeding up tests when precise user event fidelity isn't necessary. *

* * @param textBox the target {@code JTextComponent}. * @param text the text to enter. * @throws IllegalStateException if the {@code JTextComponent} is disabled. * @throws IllegalStateException if the {@code JTextComponent} is not showing on the screen. */ @RunsInEDT public void setText(@Nonnull JTextComponent textBox, @Nullable String text) { focusAndWaitForFocusGain(textBox); setTextIn(textBox, text); robot.waitForIdle(); } /** * Select the given text range. * * @param textBox the target {@code JTextComponent}. * @param text the text to select. * @throws IllegalStateException if the {@code JTextComponent} is disabled. * @throws IllegalStateException if the {@code JTextComponent} is not showing on the screen. * @throws IllegalArgumentException if the {@code JTextComponent} does not contain the given text to select. * @throws ActionFailedException if selecting the text fails. */ @RunsInEDT public void selectText(@Nonnull JTextComponent textBox, @Nonnull String text) { int indexFound = indexOfText(textBox, text); if (indexFound == -1) { throw new IllegalArgumentException(String.format("The text %s was not found", quote(text))); } selectText(textBox, indexFound, indexFound + text.length()); } @RunsInEDT private static int indexOfText(final @Nonnull JTextComponent textBox, final @Nonnull String text) { Integer result = execute(new GuiQuery() { @Override protected Integer executeInEDT() { checkEnabledAndShowing(textBox); String actualText = textBox.getText(); if (isNullOrEmpty(actualText)) { return -1; } return actualText.indexOf(text); } }); return checkNotNull(result); } /** * Select the given text range. * * @param textBox the target {@code JTextComponent}. * @param start the starting index of the selection. * @param end the ending index of the selection. * @throws IllegalStateException if the {@code JTextComponent} is disabled. * @throws IllegalStateException if the {@code JTextComponent} is not showing on the screen. * @throws ActionFailedException if selecting the text in the given range fails. */ @RunsInEDT public void selectText(@Nonnull JTextComponent textBox, int start, int end) { robot.moveMouse(textBox, checkStateAndScrollToPosition(textBox, start)); robot.moveMouse(textBox, scrollToPosition(textBox, end)); performAndValidateTextSelection(textBox, start, end); } @RunsInEDT private static @Nonnull Point checkStateAndScrollToPosition(final @Nonnull JTextComponent textBox, final int index) { Point result = execute(new GuiQuery() { @Override protected Point executeInEDT() { checkEnabledAndShowing(textBox); return scrollToVisible(textBox, index); } }); return checkNotNull(result); } @RunsInEDT private static @Nonnull Point scrollToPosition(final @Nonnull JTextComponent textBox, final int index) { Point result = execute(new GuiQuery() { @Override protected Point executeInEDT() { return scrollToVisible(textBox, index); } }); return checkNotNull(result); } /** * Move the pointer to the location of the given index. Takes care of auto-scrolling through text. * * @param textBox the target {@code JTextComponent}. * @param index the given location. * @return the position of the pointer after being moved. * @throws ActionFailedException if it was not possible to scroll to the location of the given index. */ @RunsInCurrentThread private static @Nonnull Point scrollToVisible(@Nonnull JTextComponent textBox, int index) { Rectangle indexLocation = locationOf(textBox, index); if (isRectangleVisible(textBox, indexLocation)) { return centerOf(indexLocation); } scrollToVisible(textBox, indexLocation); indexLocation = locationOf(textBox, index); if (isRectangleVisible(textBox, indexLocation)) { return centerOf(indexLocation); } String format = "Unable to make visible the location of the index <%d> by scrolling to the point <%s> on %s"; String msg = String.format(format, index, formatOriginOf(indexLocation), format(textBox)); throw actionFailure(msg); } @RunsInCurrentThread private static @Nonnull Rectangle locationOf(@Nonnull JTextComponent textBox, int index) { Rectangle r = null; try { r = textBox.modelToView(index); } catch (BadLocationException e) { throw cannotGetLocation(textBox, index); } if (r != null) { if (Platform.isMacintosh() && r.y == -1) { r.y = 0; } return r; } throw cannotGetLocation(textBox, index); } private static ActionFailedException cannotGetLocation(@Nonnull JTextComponent textBox, int index) { String msg = String.format("Unable to get location for index <%d> in %s", index, format(textBox)); throw actionFailure(msg); } @RunsInCurrentThread private static boolean isRectangleVisible(@Nonnull JTextComponent textBox, @Nonnull Rectangle r) { Rectangle visible = textBox.getVisibleRect(); return visible.contains(r.x, r.y); } private static String formatOriginOf(Rectangle r) { return concat(valueOf(r.x), ",", valueOf(r.y)); } @RunsInCurrentThread private static void scrollToVisible(@Nonnull JTextComponent textBox, @Nonnull Rectangle r) { textBox.scrollRectToVisible(r); if (isVisible(textBox, r)) { return; } scrollToVisibleIfIsTextField(textBox, r); } @RunsInCurrentThread private static void scrollToVisibleIfIsTextField(@Nonnull JTextComponent textBox, @Nonnull Rectangle r) { if (!(textBox instanceof JTextField)) { return; } Pair pointAndParent = pointAndParentForScrolling((JTextField) textBox); Container parent = pointAndParent.second; if (parent == null || parent instanceof CellRendererPane || !(parent instanceof JComponent)) { return; } ((JComponent) parent).scrollRectToVisible(addPointToRectangle(checkNotNull(pointAndParent.first), r)); } private static @Nonnull Rectangle addPointToRectangle(@Nonnull Point p, @Nonnull Rectangle r) { Rectangle destination = new Rectangle(r); destination.x += p.x; destination.y += p.y; return destination; } private static @Nonnull Point centerOf(@Nonnull Rectangle r) { return new Point(r.x + r.width / 2, r.y + r.height / 2); } @RunsInEDT private static void performAndValidateTextSelection(final @Nonnull JTextComponent textBox, final int start, final int end) { execute(new GuiTask() { @Override protected void executeInEDT() { selectTextInRange(textBox, start, end); verifyTextWasSelected(textBox, start, end); } }); } @RunsInCurrentThread private static void verifyTextWasSelected(@Nonnull JTextComponent textBox, int start, int end) { int actualStart = textBox.getSelectionStart(); int actualEnd = textBox.getSelectionEnd(); if (actualStart == min(start, end) && actualEnd == max(start, end)) { return; } String format = "Unable to select text uses indices <%d> and <%d>, current selection starts at <%d> and ends at <%d>"; String msg = String.format(format, start, end, actualStart, actualEnd); throw actionFailure(msg); } /** * Asserts that the text in the given {@code JTextComponent} is equal to the specified value. * * @param textBox the given {@code JTextComponent}. * @param expected the text to match. It can be a regular expression pattern. * @throws AssertionError if the text of the {@code JTextComponent} is not equal to the given one. */ @RunsInEDT @Override public void requireText(@Nonnull JTextComponent textBox, @Nullable String expected) { verifyThat(textOf(textBox)).as(textProperty(textBox)).isEqualOrMatches(expected); } /** * Asserts that the text in the given {@code JTextComponent} matches the given regular expression pattern. * * @param textBox the given {@code JTextComponent}. * @param pattern the regular expression pattern to match. * @throws NullPointerException if the given regular expression pattern is {@code null}. * @throws AssertionError if the text of the {@code JTextComponent} is not equal to the given one. */ @Override @RunsInEDT public void requireText(@Nonnull JTextComponent textBox, @Nonnull Pattern pattern) { verifyThat(textOf(textBox)).as(textProperty(textBox)).matches(pattern); } /** * Asserts that the given {@code JTextComponent} is empty. * * @param textBox the given {@code JTextComponent}. * @throws AssertionError if the {@code JTextComponent} is not empty. */ @RunsInEDT public void requireEmpty(@Nonnull JTextComponent textBox) { assertThat(textOf(textBox)).as(textProperty(textBox)).isEmpty(); } @RunsInEDT private static @Nonnull Description textProperty(@Nonnull JTextComponent textBox) { return propertyName(textBox, TEXT_PROPERTY); } /** * Asserts that the given {@code JTextComponent} is editable. * * @param textBox the given {@code JTextComponent}. * @throws AssertionError if the {@code JTextComponent} is not editable. */ @RunsInEDT public void requireEditable(@Nonnull JTextComponent textBox) { assertEditable(textBox, true); } /** * Asserts that the given {@code JTextComponent} is not editable. * * @param textBox the given {@code JTextComponent}. * @throws AssertionError if the {@code JTextComponent} is editable. */ @RunsInEDT public void requireNotEditable(@Nonnull JTextComponent textBox) { assertEditable(textBox, false); } @RunsInEDT private void assertEditable(@Nonnull JTextComponent textBox, boolean editable) { assertThat(isEditable(textBox)).as(editableProperty(textBox)).isEqualTo(editable); } @RunsInEDT private static @Nonnull Description editableProperty(@Nonnull JTextComponent textBox) { return propertyName(textBox, EDITABLE_PROPERTY); } /** * Returns the text of the given {@code JTextComponent}. * * @param textBox the given {@code JTextComponent}. * @return the text of the given {@code JTextComponent}. */ @RunsInEDT @Override public @Nullable String textOf(@Nonnull JTextComponent textBox) { return JTextComponentTextQuery.textOf(textBox); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy