assertj / assertj-swing

Fluent assertions for Swing apps
Other
109 stars 52 forks source link

pressAndReleaseKeys() does not work with JComboBox in AssertJ, works in FEST #233

Closed nashirj closed 5 years ago

nashirj commented 5 years ago

When trying to simulate input using AssertJ's pressAndReleaseKeys() for unit testing a JComboBox in a Java Swing program, I am not seeing the expected behavior. The program will most often hang on the pressAndReleaseKeys line and then fail, or occasionally will delete all the text currently in the JComboBox being tested, causing later assertions to fail (i.e. requireSelection()). The stack trace I receive for the provided example program (see below) when it hangs is as follows:

Focus change to javax.swing.JComboBox[name='combob', selectedItem='Bean', contents=["Pork", "Beans", "Rice"], editable=true, enabled=true, visible=true, showing=true] failed focus owner: javax.swing.plaf.metal.MetalComboBoxEditor$1(javax.swing.JTextField)[name=null, text='Bean', enabled=true, visible=true, showing=true]

org.assertj.swing.exception.ActionFailedException
at org.assertj.swing.exception.ActionFailedException.actionFailure(ActionFailedException.java:33)
at org.assertj.swing.core.BasicRobot.focus(BasicRobot.java:301)
at org.assertj.swing.core.BasicRobot.focusAndWaitForFocusGain(BasicRobot.java:270)
at org.assertj.swing.driver.ComponentDriver.focusAndWaitForFocusGain(ComponentDriver.java:419)
at org.assertj.swing.driver.ComponentDriver.pressAndReleaseKeys(ComponentDriver.java:315)
at org.assertj.swing.fixture.AbstractComponentFixture.pressAndReleaseKeys(AbstractComponentFixture.java:293)
at javapractice.ComboBoxSampleTest.testMain(ComboBoxSampleTest.java:59)

I have been using FEST and am hoping to migrate my tests to AssertJ since it is being actively maintained, whereas FEST hasn't been updated for years. I used Joel Costigliola's migration from Fest to AssertJ guide, but am having trouble when simulating keyboard input by using pressAndReleaseKeys(). I am able to simulate input when using a JTextComponentFixture i.e.

window.textBox("textB").pressAndReleaseKeys(KeyEvent.VK_LEFT);

(where window is a FrameFixture, a container in both AssertJ and FEST), but I am unable to simulate input when using a JComboBoxFixture i.e.

window.comboBox("comboB").pressAndReleaseKeys(KeyEvent.VK_LEFT);

This obstacle can usually be avoided, since most "key presses" can be simulated by using enterText i.e.

window.comboBox("comboB").enterText("\n"); //to press the enter key
window.comboBox("comboB").enterText("\b"); //to press the backspace key

but I would like to be able to use the arrow keys, control key, and other keys where I can't simulate the key press using enterText(). Is this failure due to an issue with my environment*, an issue with the way I'm using it, or is the API itself flawed?

I tried using pressKey() and then releaseKey() as a workaround, but that doesn't work with JComboBox either, and my program instead hangs on pressKey(). That being said, I am not able to use pressKey() and releaseKey() to test a JComboBox with FEST either.

*Environment details: Language version: java version "1.8.0_131" AssertJ packages: assertj-core-3.11.1.jar, assertj-swing-3.9.2.jar Operating system: Red Hat Release 6.10 (Santiago) IDE: Netbeans 8.0.2

Please let me know if there's any other information I can provide to help fix the issue!

Sample GUI application:

package javapractice;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class ComboBoxSample extends JFrame implements ItemListener{
    JPanel jp;
    JComboBox jcb;
    JLabel result;
    JLabel title;
    JTextField jtc;

    public static void main(String[] args) {
        ComboBoxSample frame = new ComboBoxSample();
    }

    ComboBoxSample() {
        super();
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setVisible(true);
        this.setTitle("Testing AssertJ");
        this.setLayout(new FlowLayout());
        jp = new JPanel();
        jcb = new JComboBox(new String[] {"Pork", "Beans", "Rice"});
        jcb.setEditable(true);
        jcb.setName("combob");
        jtc = new JTextField();
        jtc.setEditable(true);
        jtc.setPreferredSize(new Dimension(150, 25));
        jtc.setName("textb");
        title = new JLabel("Food: ");
        result = new JLabel("No food");
        jp.add(title);
        jp.add(jcb);
        jp.add(result);
        jp.add(jtc);
        this.add(jp);
        this.setLocationRelativeTo(null);
        jcb.addItemListener(this);

        this.pack();
        this.repaint();        
    }

    @Override
    public void itemStateChanged(ItemEvent e) {
        if(e.getSource() == jcb) {
            result.setText("I'm eating " + jcb.getSelectedItem());
        }
        this.pack();
    }

    public void cleanUp() {
        jcb = null;
        result = null;
        jtc = null;
        jp = null;
        title = null;
    }   
}

Test file for AssertJ:

package javapractice;

import com.sun.glass.events.KeyEvent;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * AssertJ imports.
 */
import org.assertj.swing.edt.FailOnThreadViolationRepaintManager;
import org.assertj.swing.edt.GuiActionRunner;
import org.assertj.swing.fixture.FrameFixture;

public class ComboBoxSampleTest {
    private FrameFixture window;
    private ComboBoxSample frame;

    @BeforeClass
    public static void setUpClass() {
        FailOnThreadViolationRepaintManager.install();
    }

    @AfterClass
    public static void tearDownClass() {

    }

    @Before
    public void setUp() {
        frame = GuiActionRunner.execute(() -> new ComboBoxSample());
        window = new FrameFixture(frame);
        window.show();
    }

    @After
    public void tearDown() {
        window.cleanUp();
        frame.cleanUp();
    }

    /**
     * Test of main method, of class ComboBoxSample.
     */
    @Test
    public void testMain() {
        //Delay so that we can see what's going on
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ie) {

        }

        window.textBox("textb").enterText("hi there");
        window.textBox("textb").pressAndReleaseKeys(KeyEvent.VK_BACKSPACE);
        window.comboBox().replaceText("Bean");
        //the above line is the last one to execute
        window.comboBox().pressAndReleaseKeys(KeyEvent.VK_S);
        window.comboBox().pressAndReleaseKeys(KeyEvent.VK_DOWN);
        window.comboBox().pressAndReleaseKeys(KeyEvent.VK_DOWN);
        window.comboBox().pressAndReleaseKeys(KeyEvent.VK_DOWN);
        window.comboBox().pressAndReleaseKeys(KeyEvent.VK_ENTER);
    }
}
nashirj commented 5 years ago

This is not a solution for the issue, but is a workaround that allows the desired behavior. The issue can be mitigated by invoking robot() for the comboBox().

Instead of doing

window.comboBox().pressAndReleaseKeys(KeyEvent.VK_S);

doing

window.comboBox().robot().pressAndReleaseKeys(KeyEvent.VK_S);

works as expected.

croesch commented 5 years ago

Thanks for opening the issue and posting the solution/workaround to your problem.

nashirj commented 5 years ago

Sure thing! Thanks for all your work on assertj.