Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing drag-and-drop files onto application

I am looking for a method to perform a drag-and-drop of a file/multiple files onto my application from a unit test. For example selecting some files in Windows Explorer, drag them and drop them on my application.

I am capable of testing drag-and-drop behavior between two components in my application (see below - feel free to indicate if you know a better way), but I have no idea how to do the same when the data has to come from outside my application.

I thought about using the debugger to inspect a 'file' Transferable when I do the drag-and-drop operation by hand, but there must be a better way then hard-coding a complete Transferable.

Example of a drag-and-drop test between components

import org.junit.Test;

import javax.swing.Action;
import javax.swing.JTextField;
import javax.swing.TransferHandler;
import java.awt.event.ActionEvent;

import static org.junit.Assert.assertEquals;

public class DragAndDropTest {
  @Test
  public void dragAndDropBetweenTwoTextFields() {
    JTextField firstField = new JTextField();
    JTextField secondField = new JTextField();
    String testText = "Test text";
    firstField.setText( testText );
    firstField.selectAll();
    Action copyAction = TransferHandler.getCopyAction();
    copyAction.actionPerformed( new ActionEvent( firstField, ActionEvent.ACTION_PERFORMED, "Copy" ) );
    Action pasteAction = TransferHandler.getPasteAction();
    pasteAction.actionPerformed( new ActionEvent( secondField, ActionEvent.ACTION_PERFORMED, "Paste" ) );
    assertEquals( "Text is not copied", testText, secondField.getText() );
  }
}

Edit

Based on the comments under this question, I have updated my code snippet to 'fake' a drag-and-drop by providing a hard-coded Transferable. The code also contains a little main program which just creates an empty frame on which you can drop files. The path will then be printed on the console.

On my PC, drag-and-drop of a file does not use the javaFileListFlavor but the URI flavor. Previous experiences (see this question) already teached me that the Transferable a component receives when dragging something from outside the Java application might differ in subtle ways.

So to be completely clear: I want to test the part of my code that extracts the information from the received Transferable. The 'information-handling' code (e.g. what happens when the application receives the file) can easily be tested without bothering with D&D. I just need to make sure I extract the correct information from the Transferable, and it is silly to test this with a hard-coded Transferable.

import org.junit.Test;

import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.TransferHandler;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.List;

import static org.junit.Assert.assertEquals;

public class DragAndDropTest {
  private static DataFlavor URI_LIST_FLAVOR = null;
  static {
    try {
      URI_LIST_FLAVOR = new DataFlavor( "text/uri-list;class=java.lang.String" );
    }
    catch ( ClassNotFoundException ignore ) {
    }
  }

  @Test
  public void testFileDragAndDrop() throws IOException, UnsupportedFlavorException {
    JComponent testComponent = new JPanel();
    TestingTransferHandler transferHandler = new TestingTransferHandler();
    testComponent.setTransferHandler( transferHandler );
    Clipboard clipBoard = new JLabel(  ).getToolkit().getSystemClipboard();
    Transferable transferable = new Transferable() {
      @Override
      public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[]{URI_LIST_FLAVOR};
      }

      @Override
      public boolean isDataFlavorSupported( DataFlavor flavor ) {
        return flavor == URI_LIST_FLAVOR;
      }

      @Override
      public Object getTransferData( DataFlavor flavor ) throws UnsupportedFlavorException, IOException {
        if ( flavor == URI_LIST_FLAVOR ) {
          return new String( "file:///home/robins/Desktop/swingx-1.0-javadoc.jar" );
        }
        throw new UnsupportedFlavorException( flavor );
      }
    };
    clipBoard.setContents( transferable, null );
    Action pasteAction = TransferHandler.getPasteAction();
    pasteAction.actionPerformed( new ActionEvent( testComponent, ActionEvent.ACTION_PERFORMED, "Paste" ) );
    assertEquals( transferable.getTransferData( URI_LIST_FLAVOR ), transferHandler.transferable.getTransferData( URI_LIST_FLAVOR ) );
  }

  private static class TestingTransferHandler extends TransferHandler{
    public Transferable transferable;
    @Override
    public boolean canImport( TransferSupport support ) {
      return true;
    }
    @Override
    public boolean importData( TransferSupport support ) {
      transferable = support.getTransferable();
      try{
        if ( transferable.isDataFlavorSupported( DataFlavor.javaFileListFlavor ) ) {
          System.out.println("File list flavor");
          List<File> fileList = ( List<File> ) transferable.getTransferData( DataFlavor.javaFileListFlavor );
          System.out.println( "fileList = " + fileList );
        }
        if ( transferable.isDataFlavorSupported( URI_LIST_FLAVOR )){
          System.out.println("URI list flavor");
          String uriList = ( String ) transferable.getTransferData( URI_LIST_FLAVOR );
          System.out.println( "uriList = " + uriList );
        }
        return true;
      } catch ( UnsupportedFlavorException e ) {
        return false;
      } catch ( IOException e ) {
        return false;
      }
    }
  }

  public static void main( String[] args ) {
    EventQueue.invokeLater( new Runnable() {
      @Override
      public void run() {
        JFrame frame = new JFrame( "TestFrame" );
        JPanel contentPane = new JPanel( new BorderLayout(  ) );
        contentPane.setTransferHandler( new TestingTransferHandler() );
        frame.setContentPane( contentPane );
        frame.setSize( 200,200 );
        frame.setVisible( true );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      }
    } );
  }
}
like image 280
Robin Avatar asked Aug 02 '12 06:08

Robin


1 Answers

Unit-testing Swing GUI is always painful, let alone D&D. However, I believe it can be done.

There are two things to notice:

  1. Currently, you're not testing D&D at all, but rather copy-paste. This is also a form of data transfer, but the use of copyAction and pasteAction causes you to really not go through any of the steps of D&D

  2. You mention that you want to check the part that receives the file, and therefore you created a custom Transferable, but I would like to argue that you're trying to test the wrong thing. The part that handles the data can be implemented as a package private method handling a Transferable containing the data, and tested with a simple unit test (no Swing components involved)

I believe that what you should be testing, if you care about D&D differences between different OS, is that the D&D process itself is working, i.e.:

  1. The component with the data (the DragSource) is requested to provide the data, and that it provides it
  2. The component requesting the data (the DropTarget) receives the data and handles it
  3. Optionally, you might want to check that different conditions are allowed or disallowed (e.g. copy vs. move, etc.)

You can test this by either using FEST's ComponentDragAndDrop class (see here).

If you want to write you own implementation (why would you want to do that ?!) you could:
Replace both the source and target components' TransferHandlers with mocks (or spies to be more accurate), that on top of calling the real methods, would enable you to verify that the expected methods are called with the expected data

like image 159
ethanfar Avatar answered Oct 12 '22 23:10

ethanfar