On this page, we will implement ProfileEditorDialogTestGUI, a GUI manager to facilitate a JUnit test for the ProfileEditorDialog. The class will be a subclass of ProfileEditorTestBase and use the utility class ProfileFileManagerTestData, which we developed as part of the JUnit test for the ProfileFileManager.
See also:
- The ProfileEditorTestBase Class page.
- The ProfileFileManagerTestData Class on the ProfileFileManager JUnit Test page.
GitHub repository: Cartesian Plane Lesson 18
Previous page: Cartesian Plane Lesson 18 Page 18: The ProfileEditorDialog
Class ProfileEditorDialogTestGUI
This class supports the ProfileEditorDialog JUnit test. It is a concrete subclass of ProfileEditorTestBase, implemented as a singleton and located in the β¦test_utils package on the test source path. Below, we discuss its infrastructure, constructor, and public elements.
Infrastructure
Find a discussion of the ProfileEditorDialogTestGUI’s fields and helper methods below.
β¬ Fields
The following is an annotated list of the ProfileEditorDialogTestGUI fields.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public class ProfileEditorDialogTestGUI extends ProfileEditorTestBase { private static final long pauseInterval = 125; private static ProfileEditorDialogTestGUI testGUI; private static final File testDataDir = ProfileFileManagerTestData.getTestDataDir(); private final ProfileEditorDialog testDialog; private final ProfileFileManager fileMgr; private final JButton okButton; private final JButton applyButton; private final JButton resetButton; private final JButton cancelButton; private final JButton openButton; private final JButton saveAsButton; private final JButton saveButton; private final JButton closeButton; private AbstractButton errorDialogOKButton; private JFileChooser fileChooser; private JDialog chooserDialog = null; private JTextField chooserName = null; private JButton openFileButton = null; private JButton saveFileButton = null; private int lastDialogResult; // ... } |
- Line 3: Number of milliseconds to wait for a GUI operation to complete.
- Line 4: This class’s singleton. Initialized and returned by getTestGUI.
- Lines 5,6: The test data subdirectory containing test files, declared here for convenience. See also The ProfileFileManagerTestData Class.
- Line 8: The ProfileEditorDialog under test.
- Line 9: The ProfileFileManager object obtained from the ProfileEditorDialog under test.
- Lines 11-14: The control buttons that constitute the interface between the dialog and the PropertyManager.
- Lines 15-18: The control buttons that constitute the interface between the dialog and the ProfileFileManager.
- Line 20: The OK button from the error dialog. This field is updated every time an error dialog is posted; see getErrorDialogAndOKButton.
- Line 22: The JFileChooser from the ProfileFileManager.
- Line 24: The dialog that contains the JFileChooser. We don’t know when this dialog will be instantiated, so we don’t try to initialize it when our ProfileEditorDialogTestGUI is instantiated. Instead, we will only try to obtain a reference after we expect it to be posted. See getChooserDialog.
- Lines 25-27: Components of the file chooser and its dialog that we need to perform testing. The configuration of these components is dynamic; for example, sometimes there’s a Save button, and sometimes there’s not. References to these components are obtained every time they’re needed. See getFileChooserComponents and execFileOp.
- Line 29: The last result returned by the dialog under test. Set in postDialog, returned by getLastDialogResult.
βΉ Private Methods
Below, we discuss the helper methods for the ProfileEditorDialogTestGUI class.
π¦ private static void initGUI( Profile profile )
This method a) instantiates the ProfileEditorDialog under test and b) initializes this class’s singleton. It must be invoked in the context of the EDT. See also Constructor. Here is the implementation of this method.
private static void initGUI( Profile profile )
{
ProfileEditorDialog dialog =
new ProfileEditorDialog( null, profile );
testGUI = new ProfileEditorDialogTestGUI( dialog );
}
π¦ private JButton getButton( String text )
This method searches the ProfileEditorDialog’s component tree for the JButton with the given text. It raises an assertion if the button can’t be found. It looks like this.
private JButton getButton( String text )
{
Predicate<JComponent> pred =
ComponentFinder.getButtonPredicate( text );
JComponent comp =
ComponentFinder.find( testDialog, pred );
assertNotNull( comp );
assertTrue( comp instanceof JButton );
return (JButton)comp;
}
π¦ private JDialog getChooserDialog()
The getChooserDialog method attempts to locate the dialog that encapsulates the file chooser. Because we don’t know when this dialog will be instantiated, we only look for it after we expect the file chooser dialog to be posted. We find the dialog by looking for a visible JDialog with the expected title (lines 3-10, below). See also getFileChooserComponents and execFileOp. The code for this method follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | private JDialog getChooserDialog() { final boolean canBeFrame = false; final boolean canBeDialog = true; final boolean mustBeVis = true; final String title = fileChooser.getDialogTitle(); final Predicate<Window> isDialog = w -> (w instanceof JDialog); final Predicate<Window> hasTitle = w -> title.equals( ((JDialog)w).getTitle() ); final Predicate<Window> pred = isDialog.and( hasTitle ); ComponentFinder finder = new ComponentFinder( canBeDialog, canBeFrame, mustBeVis ); Window window = finder.findWindow( pred ); assertNotNull( window ); assertTrue( window instanceof JDialog ); return (JDialog)window; } |
π¦ private void getFileChooserComponents()
This method locates those components of the file chooser and its dialog that are needed for testing. The configuration of these components is unpredictable; for example, the Open button is sometimes present and sometimes not. So, we’ll need to identify the components at the time they are needed. See also execFileOp. The annotated listing for the getFileChooserComponents method follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | private void getFileChooserComponents() { final Predicate<JComponent> typePred = c -> (c instanceof JTextField ); final Predicate<JComponent> openPred = ComponentFinder.getButtonPredicate( "Open" ); final Predicate<JComponent> savePred = ComponentFinder.getButtonPredicate( "Save" ); // Get chooser dialog if necessary if ( chooserDialog == null ) chooserDialog = getChooserDialog(); // The text field component should always be present. Component comp = ComponentFinder.find( fileChooser, typePred ); assertNotNull( comp ); assertTrue( comp instanceof JTextField ); chooserName = (JTextField)comp; // The Open and Save buttons may not both be present, but // at least one of them must be. openFileButton = null; comp = ComponentFinder.find( fileChooser, openPred ); if ( comp != null ) { assertTrue( comp instanceof JButton ); openFileButton = (JButton)comp; } saveFileButton = null; comp = ComponentFinder.find( fileChooser, savePred ); if ( comp != null ) { assertTrue( comp instanceof JButton ); saveFileButton = (JButton)comp; } assertTrue( openFileButton != null || saveFileButton != null ); } |
- Lines 3,4: Formulate a predicate that returns true when a candidate component is a JTextField.
- Lines 5,6: Formulate a predicate that returns true when a candidate component is a JButton labeled Open.
- Lines 7,8: Formulate a predicate that returns true when a candidate component is a JButton labeled Save.
- Lines 11,12: If necessary, locate the file chooser dialog; this operation only needs to be executed once.
- Lines 15,16: Search the file chooser dialog component hierarchy for a JTextField. This is the component in which the operator can enter a file path. We should always be able to locate this component.
- Lines 17,18: Sanity check; verify that the JTextField was found.
- Line 19: Configure the JTextField reference.
- Line 23: Initialize the openFileButton field to null.
- Line 24: Look for the Open button. This component might or might not be found.
- Line 25: If the Open button is found:
- Line 27: Sanity check; verify that the component is a JButton.
- Line 28: Store the component reference in the openFileButton field.
- Line 30: Initialize the saveFileButton field to null.
- Line 31: Look for the Save button. This component might or might not be found.
- Line 32: If the Save button is found:
- Line 34: Sanity check; verify that the component is a JButton.
- Line 35: Store the component reference in the saveFileButton field.
- Line 37: Verify that we’ve found at least one of the Open and Save buttons.
π¦ private void getErrorDialogAndOKButton()
This method attempts to locate an error dialog. It assumes that, if present, the error dialog is the only visible dialog other than the ProfileEditorDialog (see below, lines 10 and 11). If the dialog is found, the method obtains a reference to its OK button and stores it in the errorDialogOKButton field. If the dialog is not found, the errorDialogOKButton field is set to null. See also dismissErrorDialog. Following is an annotated listing of this method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | private void getErrorDialogAndOKButton() { final boolean canBeFrame = false; final boolean canBeDialog = true; final boolean mustBeVis = true; GUIUtils.schedEDTAndWait( () -> { errorDialogOKButton = null; ComponentFinder finder = new ComponentFinder( canBeDialog, canBeFrame, mustBeVis ); Predicate<Window> wPred = w -> !(w instanceof ProfileEditorDialog); Window window = finder.findWindow( wPred ); if ( window != null ) { assertTrue( window instanceof JDialog ); Predicate<JComponent> pred = ComponentFinder.getButtonPredicate( "OK" ); JComponent comp = ComponentFinder.find( window, pred ); assertNotNull( comp ); assertTrue( comp instanceof AbstractButton ); errorDialogOKButton = (AbstractButton)comp; } }); } |
- Lines 3-5: Configuration parameters for constructing a ComponentFinder to locate the error dialog. The window we’re looking forΒ cannotΒ be a frame; itΒ canΒ be a dialog andΒ mustΒ be visible.
- Lines 6-24: Execute the following operation on the EDT:
- Line 7: Initialize the errorDialogOKButton to not found.
- Lines 8,9: Instantiate a ComponentFinder to search for the error dialog.
- Lines 10,11: Formulate a predicate that eliminates the ProfileEditorDialog from the search.
- Line 12: Search for a top-level window that is a dialog, is visible, and is not the ProfileEditorDialog.
- Line 13: Determine whether a window matching the above criteria can be found; it is not an error if a window is not found. If a candidate window is found:
- Line 15: Sanity check; verify that the window obtained is a JDialog.
- Lines 16,17: Formulate a predicate that looks for an AbstractButton with the text OK.
- Lines 18,19: Execute the search for the AbstractButton.
- Line 20: Verify that a component was found (if a candidate dialog exists, it must have an OK button).
- Line 21: Sanity check; verify that the component is an AbstractButton.
- Line 22: Configure the errorDialogOKButton field.
π¦ private boolean dismissErrorDialog()
If an error dialog is visible, the dismissErrorDialog method pushes its OK button. At the start, it pauses, giving the error dialog time to become visible. If the error dialog is found and dismissed, true is returned; otherwise, false is returned. The implementation of this method follows. See also getErrorDialogAndOKButton.
private boolean dismissErrorDialog()
{
boolean dismissed = false;
Utils.pause( pauseInterval );
getErrorDialogAndOKButton();
if ( errorDialogOKButton != null )
{
dismissed = true;
pushButton( errorDialogOKButton );
}
return dismissed;
}
π¦ private static void pushButton( AbstractButton button )
This method activates a button in the context of the EDT. Here’s the code.
private static void pushButton( AbstractButton button )
{
GUIUtils.schedEDTAndWait( () -> button.doClick() );
}
π¦ private void enterPath( String fileName )
This method enters a path into the file chooser text field that encapsulates a file path. Modification of the text field takes place on the EDT. It looks like this:
private void enterPath( String fileName )
{
assertNotNull( chooserName );
File file = new File( testDataDir, fileName );
String path = file.getAbsolutePath();
GUIUtils.schedEDTAndWait( () -> chooserName.setText( path ) );
}
βΉ Constructor
This class has one constructor, which is private. It must be invoked in the context of the EDT. A listing follows. See also initGUI.
private ProfileEditorDialogTestGUI( ProfileEditorDialog dialog )
{
super( dialog.getProfileEditor() );
testDialog = dialog;
fileMgr = testDialog.getFileManager();
fileChooser = fileMgr.getFileChooser();
okButton = getButton( "OK" );
applyButton = getButton( "Apply" );
resetButton = getButton( "Reset" );
cancelButton = getButton( "Cancel" );
openButton = getButton( "Open File" );
saveButton = getButton( "Save" );
saveAsButton = getButton( "Save As" );
closeButton = getButton( "Close File" );
}
Public Elements
The ProfileEditorDialogTestGUI class has no public constructors. Below, you will find a discussion of its public methods.
βΉ Public Methods
This section of the page lists the public methods of the ProfileEditorDialogTestGUI class.
π¦ public static ProfileEditorDialogTestGUI getTestGUI( Profile profile )
This method returns the ProfileEditorDialogTestGUI class’s singleton, instantiating it if necessary. This method may be called on the EDT, but it is not necessary to do so. See also initGUI. Here’s the code.
public static ProfileEditorDialogTestGUI getTestGUI( Profile profile )
{
if ( testGUI != null )
;
else if ( SwingUtilities.isEventDispatchThread() )
initGUI( profile );
else
GUIUtils.schedEDTAndWait( () -> initGUI( profile ) );
return testGUI;
}
π¦ public void cancelDialog()
This method is not an alternative to pressing the test dialog’s Cancel button. Its purpose is to close the dialog if it was inadvertently left open because of a test failure. It should be called in the JUnit test’s after-each method. If the test dialog is open, we close its FontEditorDialog (if open), push its Cancel button, and wait for it to close. Calling this method when the dialog is not open has no effect. The code looks like this.
public void cancelDialog()
{
cancelFontDialog();
if ( testDialog.isVisible() )
{
pushCancelButton();
while ( testDialog.isVisible() )
Utils.pause( 2 );
}
}
π¦ public boolean isVisible()
This method returns true if the dialog under test is visible. It executes in the context of the EDT. It looks like this.
public boolean isVisible()
{
boolean[] isVisible = new boolean[1];
GUIUtils.schedEDTAndWait( () ->
isVisible[0] = testDialog.isVisible()
);
}
π¦ public Thread postDialog()
The postDialog method looks like many similar methods we’ve already seen. It starts the dialog under test in a dedicated thread, waits for the dialog to become visible, and returns the thread ID. A listing of this method follows. See also getLastDialogResult.
public Thread postDialog()
{
Thread thread = new Thread(
() -> lastDialogResult = testDialog.showDialog()
);
GUIUtils.schedEDTAndWait( () -> thread.start() );
while ( !testDialog.isVisible() )
Utils.pause( 1 );
return thread;
}
π¦ public int getLastDialogResult()
This method returns the last result obtained when the dialog under test was closed. The code follows. See also postDialog.
public int getLastDialogResult()
{
return lastDialogResult;
}
π¦ public void pushOKButton()
π¦ public void pushCancelButton()
π¦ public void pushApplyButton()
π¦ public void pushResetButton()
These methods click the named button in the context of the EDT. The implementation of pushOKButton follows; the others follow a similar pattern and can be found in the GitHub repository.
public void pushOKButton()
{
pushButton( okButton );
}
π¦ public void pushOpenButton()
π¦ public void pushSaveButton()
π¦ public void pushSaveAsButton()
π¦ public void pushCloseButton()
These methods activate the named button in the context of the EDT. The implementation of pushOpenButton follows; the others follow a similar pattern and can be found in the GitHub repository.
public void pushOpenButton()
{
pushButton( openButton );
}
π¦ public int getLastDialogResult()
This method returns the last result obtained when the dialog under test was closed. It simply returns the value of the lastDialogResult field; the code is in the GitHub repository. See also postDialog.
π¦ public File getCurrFile()
The getCurrFile method returns the current-file property of the dialog’s encapsulated file manager. Here’s a listing of this method.
public File getCurrFile()
{
return fileMgr.getCurrFile();
}
π¦ public void execFileOp(Runnable runner, File file, boolean expectChooser, boolean approve, boolean expectError)
This public method can be invoked directly by the client. However, the client will generally choose one of the convenience methods that encapsulate it, such as openFile, openFileGoWrong, saveFile, or saveAsFile. It executes a file operation in a dedicated thread and does not return until the operation concludes and the thread expires. The caller specifies the operation to execute; it is the caller’s responsibility to ensure that the specified operation is executed on the EDT. The specified operation usually involves a button press, such as () -> pushSaveButton(), which will push the Save As button in the context of the EDT. The execFileOp method is aware of whether or not to expect interaction with the file chooser and whether or not to expect the operation to terminate with an error. I’ve included an annotated listing of this method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public void execFileOp( Runnable runner, File file, boolean expectChooser, boolean approve, boolean expectError ) { Thread thread = new Thread( runner ); thread.start(); if ( expectChooser ) { Utils.pause( pauseInterval ); String name = file.getName(); getFileChooserComponents(); assertTrue( chooserDialog.isVisible() ); enterPath( name ); AbstractButton approveButton = openFileButton != null ? openFileButton : saveFileButton; AbstractButton targetButton = approve ? approveButton : cancelFileButton; assertNotNull( targetButton ); pushButton( targetButton ); } boolean dismissStatus = dismissErrorDialog(); assertEquals( expectError, dismissStatus ); Utils.join( thread ); } |
- Lines 1-7:
- The runner parameter encapsulates the operation, for example, testGUI::pushOpenButton. The caller is responsible for ensuring the operation is executed on the EDT.
- The file parameter indicates the data file on which the operation will be performed. It is only used if the operation requires interaction with the file chooser (see lines 15 and 18).
- The expectChooser parameter must be true if the given operation requires interaction with the file chooser.
- The approve parameter is true to indicate that interaction with the file chooser should terminate by pushing the file chooser’s Save or Open button. If false, the operation will terminate by pressing the Cancel button. This parameter is not used if expectChooser (line 4) is false.
- The expectError parameter is true if the given operation is expected to generate an error.
- Lines 9,10: Create and start the dedicated thread that drives the file operation.
- Lines 12-25: If file chooser interaction is expected:
- Line 14: Give the chooser dialog a chance to post.
- Line 15: Get the file name to enter into the chooser’s text field.
- Line 16: Get the necessary file chooser dialog components (see getFileChooserComponents).
- Line 17: Sanity check; verify that the file chooser dialog is visible.
- Line 18: Enter the file path into the file chooser’s text field.
- Lines 19-22: Decide which of the dialog’s control buttons to push. If approve is true, we want either the Open or Save button, exactly one of which is expected to be present. If approve is false, we want to push the Cancel button, which we expect always to be present.
- Line 23: Sanity check: verify that we have a non-null target button.
- Line 24: Push the target button.
- Line 27: Determine if an error dialog is posted. If so, push its OK button.
- Line 28: Verify that the presence or absence of an error dialog conforms to expectations.
- Line 29: Wait for the dedicated thread to expire.
π¦ public void openFile( File file )
π¦ public void openFileGoWrong( File file )
These methods encapsulate the invocation of execFileOp to open a file. In each case, the open operation is initiated by pushing the dialog’s Open File button. The openFile method assumes the operation will complete successfully, while openFileGoWrong assumes it will fail with an I/O error. Examples of calling these methods are: testGUI.openFile( distinctFile );
testGUI.openFileGoWrong( noSuchFile );
The code for these two methods follows.
public void openFile( File file )
{
execFileOp(
this::pushOpenButton,
file,
true, // expectChooser
true, // approve
false // expectError
);
}
public void openFileGoWrong( File file )
{
execFileOp(
this::pushOpenButton,
file,
true, // expectChooser
true, // approve
true // expectError
);
}
π¦ public void save( File file )
π¦ public void saveGoWrong( File file )
These methods encapsulate the invocation of execFileOp to save a file. In each case, the operation is initiated by pushing the dialog’s Save button. The save method assumes the operation will complete successfully, while saveGoWrong assumes it will fail with an I/O error. The methods are sensitive to whether or not a file is currently open (i.e., whether or not to expect interaction with the file chooser). Examples of calling these methods are: testGUI.save( adHocFile );
testGUI.saveGoWrong( readOnlyFile );
Here is the code for these methods.
public void save( File file )
{
boolean expectChooser = fileMgr.getCurrFile() == null;
execFileOp(
this::pushSaveButton,
file,
expectChooser,
true, // approve
false // expectError
);
}
public void saveGoWrong( File file )
{
boolean expectChooser = fileMgr.getCurrFile() == null;
execFileOp(
this::pushSaveButton,
file,
expectChooser,
true, // approve
true // expectError
);
}
π¦ public void saveAs( File file )
π¦ public void saveAsGoWrong( File file )
These methods encapsulate the invocation of execFileOp to perform a save-as operation. In each case, the operation is initiated by pushing the dialog’s Save As button. The saveAs method assumes the operation will complete successfully, while saveAsGoWrong assumes it will fail with an I/O error. Examples of calling these methods are: testGUI.saveAs( adHocFile );
testGUI.saveAsGoWrong( readOnlyFile );
Implementation of these methods is an exercise for the student. The solution can be found in the GitHub repository.
π¦ public void cancel( File file, Runnable runner )
This method encapsulates the invocation of execFileOp to begin a file I/O operation and then cancel it via the file chooser. The client specifies a target file and a Runnable encapsulating the operation to begin. Examples of calling this method are: testGUI.cancel( adHocFile, testGUI::pushOpenButton );
testGUI.cancel( adHocFile, testGUI::pushSaveAsButton );
Here is the code for this method.
public void cancel( File file, Runnable runner )
{
execFileOp(
runner,
file,
true, // expectChooser
false, // approve
false // expectError
);
}
Summary
On this page, we implemented ProfileEditorDialogTestGUI, which extends the abstract class ProfileEditorTestBase. It will provide support for the ProfileEditorDialog JUnit test, class ProfileEditorDialogTest, the implementation of which is our next topic.