/** * 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.driver; import static javax.swing.text.DefaultEditorKit.selectAllAction; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.util.Lists.newArrayList; import static org.assertj.core.util.Strings.concat; import static org.assertj.core.util.Strings.quote; import static org.assertj.swing.driver.ComponentPreconditions.checkEnabledAndShowing; import static org.assertj.swing.driver.JSpinnerSetValueTask.setValue; import static org.assertj.swing.driver.JSpinnerValueQuery.valueOf; 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.Component; import java.util.List; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.swing.JSpinner; import javax.swing.text.JTextComponent; import org.assertj.swing.annotation.RunsInCurrentThread; import org.assertj.swing.annotation.RunsInEDT; import org.assertj.swing.core.ComponentFinder; import org.assertj.swing.core.Robot; import org.assertj.swing.core.TypeMatcher; /** * <p> * Supports functional testing of {@code JSpinner}s. * </p> * * <p> * <b>Note:</b> This class is intended for internal use only. Please use the classes in the package * {@link org.assertj.swing.fixture} in your tests. * </p> * * @author Alex Ruiz * @author Yvonne Wang */ public class JSpinnerDriver extends JComponentDriver { private static final TypeMatcher EDITOR_MATCHER = new TypeMatcher(JTextComponent.class, true); private static final String VALUE_PROPERTY = "value"; /** * Creates a new {@link JSpinnerDriver}. * * @param robot the robot to use to simulate user input. */ public JSpinnerDriver(@Nonnull Robot robot) { super(robot); } /** * Increments the value of the {@code JSpinner} the given number of times. * * @param spinner the target {@code JSpinner}. * @param times how many times the value of this fixture's {@code JSpinner} should be incremented. * @throws IllegalArgumentException if {@code times} is less than or equal to zero. * @throws IllegalStateException if the {@code JSpinner} is disabled. * @throws IllegalStateException if the {@code JSpinner} is not showing on the screen. */ @RunsInEDT public void increment(@Nonnull JSpinner spinner, int times) { checkIsPositive(times, "increment the value"); validateAndIncrementValue(spinner, times); robot.waitForIdle(); } @RunsInEDT private static void validateAndIncrementValue(final @Nonnull JSpinner spinner, final int times) { execute(() -> { checkEnabledAndShowing(spinner); incrementValue(spinner, times); }); } @RunsInCurrentThread private static void incrementValue(@Nonnull JSpinner spinner, int times) { for (int i = 0; i < times; i++) { Object newValue = spinner.getNextValue(); if (newValue == null) { return; } spinner.setValue(newValue); } } /** * Increments the value of the {@code JSpinner}. * * @param spinner the target {@code JSpinner}. * @throws IllegalStateException if the {@code JSpinner} is disabled. * @throws IllegalStateException if the {@code JSpinner} is not showing on the screen. */ @RunsInEDT public void increment(@Nonnull JSpinner spinner) { validateAndIncrementValue(spinner); robot.waitForIdle(); } @RunsInEDT private static void validateAndIncrementValue(final @Nonnull JSpinner spinner) { execute(() -> { checkEnabledAndShowing(spinner); Object newValue = spinner.getNextValue(); if (newValue != null) { spinner.setValue(newValue); } }); } /** * Decrements the value of the {@code JSpinner} the given number of times. * * @param spinner the target {@code JSpinner}. * @param times how many times the value of this fixture's {@code JSpinner} should be decremented. * @throws IllegalArgumentException if {@code times} is less than or equal to zero. * @throws IllegalStateException if the {@code JSpinner} is disabled. * @throws IllegalStateException if the {@code JSpinner} is not showing on the screen. */ @RunsInEDT public void decrement(@Nonnull JSpinner spinner, int times) { checkIsPositive(times, "decrement the value"); validateAndDecrementValue(spinner, times); robot.waitForIdle(); } private void checkIsPositive(int times, @Nonnull String action) { if (times > 0) { return; } String msg = String.format("The number of times to %s should be greater than zero, but was <%d>", action, times); throw new IllegalArgumentException(msg); } @RunsInEDT private static void validateAndDecrementValue(final @Nonnull JSpinner spinner, final int times) { execute(() -> { checkEnabledAndShowing(spinner); decrementValue(spinner, times); }); } @RunsInCurrentThread private static void decrementValue(@Nonnull JSpinner spinner, int times) { for (int i = 0; i < times; i++) { Object newValue = spinner.getPreviousValue(); if (newValue == null) { return; } spinner.setValue(newValue); } } /** * Decrements the value of the {@code JSpinner}. * * @param spinner the target {@code JSpinner}. * @throws IllegalStateException if the {@code JSpinner} is disabled. * @throws IllegalStateException if the {@code JSpinner} is not showing on the screen. */ @RunsInEDT public void decrement(@Nonnull JSpinner spinner) { validateAndDecrementValue(spinner); robot.waitForIdle(); } @RunsInEDT private static void validateAndDecrementValue(final @Nonnull JSpinner spinner) { execute(() -> { checkEnabledAndShowing(spinner); Object newValue = spinner.getPreviousValue(); if (newValue != null) { spinner.setValue(newValue); } }); } /** * Returns the text displayed in the given {@code JSpinner}. This method first tries to get the text displayed in the * {@code JSpinner}'s editor, assuming it is a {@code JTextComponent}. If the text from the editor cannot be * retrieved, it will return the {@code String} representation of the value in the {@code JSpinner}'s model. * * @param spinner the target {@code JSpinner}. * @return the text displayed in the given {@code JSpinner}. */ @RunsInEDT public @Nullable String textOf(@Nonnull JSpinner spinner) { JTextComponent editor = findEditor(spinner); if (editor != null) { return JTextComponentTextQuery.textOf(editor); } Object value = valueOf(spinner); return value != null ? value.toString() : null; } /** * Enters and commits the given text in the {@code JSpinner}, assuming its editor has a {@code JTextComponent} under * it. * * @param spinner the target {@code JSpinner}. * @param text the text to enter. * @throws IllegalStateException if the {@code JSpinner} is disabled. Or if the {@code JSpinner} is not showing on the * screen. * @throws org.assertj.swing.exception.ActionFailedException if the editor of the {@code JSpinner} is not a * {@code JTextComponent} or cannot be found. */ @RunsInEDT public void enterTextAndCommit(@Nonnull JSpinner spinner, String text) { enterText(spinner, text); commit(spinner); robot.waitForIdle(); } @RunsInEDT private static void commit(final @Nonnull JSpinner spinner) { execute(() -> spinner.commitEdit()); } /** * Enters the given text in the {@code JSpinner}, assuming its editor has a {@code JTextComponent} under it. This * method does not commit the value to the {@code JSpinner}. * * @param spinner the target {@code JSpinner}. * @param text the text to enter. * @throws IllegalStateException if the {@code JSpinner} is disabled. Or if the {@code JSpinner} is not showing on the * screen. * @throws org.assertj.swing.exception.ActionFailedException if the editor of the {@code JSpinner} is not a * {@code JTextComponent} or cannot be * found. * @see #enterTextAndCommit(JSpinner, String) */ @RunsInEDT public void enterText(@Nonnull JSpinner spinner, @Nonnull String text) { checkInEdtEnabledAndShowing(spinner); JTextComponent editor = findEditor(spinner); robot.waitForIdle(); checkEditorNotNull(spinner, editor); if (editor == null) { return; } robot.focusAndWaitForFocusGain(editor); invokeAction(editor, selectAllAction); robot.enterText(text); } @RunsInEDT private @Nullable JTextComponent findEditor(@Nonnull JSpinner spinner) { ComponentFinder finder = robot.finder(); List<Component> found = newArrayList(finder.findAll(spinner, EDITOR_MATCHER)); if (found.size() != 1) { return null; } Component c = found.get(0); if (c instanceof JTextComponent) { return (JTextComponent) c; } return null; } @RunsInEDT private static void checkEditorNotNull(final @Nonnull JSpinner spinner, final @Nullable JTextComponent editor) { execute(() -> { if (editor == null) { throw actionFailure(concat("Unable to find editor for ", format(spinner))); } }); } /** * Selects the given value in the given {@code JSpinner}. * * @param spinner the target {@code JSpinner}. * @param value the value to select. * @throws IllegalStateException if the {@code JSpinner} is disabled. * @throws IllegalStateException if the {@code JSpinner} is not showing on the screen. * @throws IllegalArgumentException if the given {@code JSpinner} does not support the given value. */ @RunsInEDT public void selectValue(@Nonnull JSpinner spinner, @Nonnull Object value) { try { setValue(spinner, value); } catch (IllegalArgumentException e) { // message from original exception is useless throw new IllegalArgumentException(concat("Value ", quote(value), " is not valid")); } robot.waitForIdle(); } /** * Returns the {@code JTextComponent} used as editor in the given {@code JSpinner}. * * @param spinner the target {@code JSpinner}. * @return the {@code JTextComponent} used as editor in the given {@code JSpinner}. * @throws org.assertj.swing.exception.ComponentLookupException if the given {@code JSpinner} does not have a * {@code JTextComponent} as editor. */ @RunsInEDT public JTextComponent editor(@Nonnull JSpinner spinner) { return (JTextComponent) robot.finder().find(spinner, EDITOR_MATCHER); } /** * Verifies that the value of the {@code JSpinner} is equal to the given one. * * @param spinner the target {@code JSpinner}. * @param value the expected value. * @throws AssertionError if the value of the {@code JSpinner} is not equal to the given one. */ @RunsInEDT public void requireValue(@Nonnull JSpinner spinner, Object value) { assertThat(valueOf(spinner)).as(propertyName(spinner, VALUE_PROPERTY)).isEqualTo(value); } }