/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.swing.dialog;
import java.awt.Dialog;
import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.geotools.factory.Hints;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.swing.testutils.GraphicsTestBase;
import org.geotools.swing.testutils.GraphicsTestRunner;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.fest.swing.fixture.DialogFixture;
import org.fest.swing.fixture.JButtonFixture;
import org.fest.swing.fixture.JListFixture;
import org.fest.swing.fixture.JTextComponentFixture;
import org.geotools.swing.testutils.WindowActivatedListener;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Tests for {@linkplain JCRSChooser}.
* <p>
* This test class uses an {@linkplain ExecutorService} to launch the dialog which
* avoids a deadlock between the dialog waiting for a user response and this class
* waiting for the dialog to show up on the event thread.
*
* @author Michael Bedward
* @since 8.0
* @source $URL$
* @version $Id$
*/
@RunWith(GraphicsTestRunner.class)
public class JCRSChooserTest extends GraphicsTestBase<Dialog> {
// Max waiting time for return value
private static final long RETURN_TIMEOUT = 1000;
// A filter string that resolves to just one CRS list entry with EPSG
private static final String UNIQUE_FILTER_STRING = "32766";
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
private static final Random rand = new Random();
private static CRSAuthorityFactory FACTORY;
private static List<String> CODES;
private WindowActivatedListener listener;
@BeforeClass
public static void setUpOnce() throws Exception {
// Specify longitude-first order for the authority factory
Hints hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
FACTORY = ReferencingFactoryFinder.getCRSAuthorityFactory(
JCRSChooser.DEFAULT_AUTHORITY, hints);
CODES = new ArrayList<String>(FACTORY.getAuthorityCodes(CoordinateReferenceSystem.class));
}
@Before
public void setup() {
listener = new WindowActivatedListener(JCRSChooser.CRSDialog.class);
Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.WINDOW_EVENT_MASK);
}
@After
public void cleanup() {
Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
}
@Test
public void checkDefaultDialogIsSetupCorrectly() throws Exception {
showDialog();
((DialogFixture) windowFixture).requireModal();
assertEquals(JCRSChooser.DEFAULT_TITLE, windowFixture.component().getTitle());
// The text box should be editable and initially empty
JTextComponentFixture textBox = windowFixture.textBox();
assertNotNull(textBox);
textBox.requireEditable();
textBox.requireText("");
// The list should have as many items as there are authority codes
JListFixture list = windowFixture.list();
assertNotNull(list);
list.requireItemCount(CODES.size());
}
@Test
public void customTitle() throws Exception {
final String TITLE = "Custom title";
showDialog(TITLE);
assertEquals(TITLE, windowFixture.component().getTitle());
}
@Test
public void cancellingDialogReturnsNull() throws Exception {
Future<CoordinateReferenceSystem> future = showDialog();
JButtonFixture button = getButton("Cancel");
button.click();
CoordinateReferenceSystem crs = retrieveCRS(future);
assertNull(crs);
}
@Test
public void okButtonDisabledInitially() throws Exception {
showDialog();
windowFixture.list().requireNoSelection();
getButton("OK").requireDisabled();
}
@Test
public void okButtonEnabledWhenListHasSelection() throws Exception {
showDialog();
windowFixture.list().clickItem(0);
windowFixture.robot.waitForIdle();
getButton("OK").requireEnabled();
}
@Test
public void okButtonStateWithListSize() throws Exception {
showDialog();
JButtonFixture button = getButton("OK");
// Filter to a single item - button should be enabled
JTextComponentFixture textBox = windowFixture.textBox();
textBox.enterText(UNIQUE_FILTER_STRING);
windowFixture.robot.waitForIdle();
button.requireEnabled();
// Remove filter (no list selection) - button should be disabled
textBox.deleteText();
windowFixture.robot.waitForIdle();
windowFixture.list().requireNoSelection();
button.requireDisabled();
}
@Test
public void okButtonDisabledWhenListSelectionIsCleared() throws Exception {
showDialog();
// Make a list selection, then clear it
JListFixture list = windowFixture.list();
list.clickItem(0);
windowFixture.robot.waitForIdle();
list.clearSelection();
windowFixture.robot.waitForIdle();
getButton("OK").requireDisabled();
}
@Test
public void unmatchedFilterTextEmptiesListAndDisablesOKButton() throws Exception {
showDialog();
// Some filter text that will not match any CRS
windowFixture.textBox().enterText("FooBar");
windowFixture.robot.waitForIdle();
windowFixture.list().requireItemCount(0);
getButton("OK").requireDisabled();
}
@Test
public void filterByCode() throws Exception {
showDialog();
JListFixture list = windowFixture.list();
final String code = getRandomCode().toLowerCase();
windowFixture.textBox().enterText(code);
windowFixture.robot.waitForIdle();
// Note: there may be more than one list item if the code
// we just typed is also the start of longer codes or if the
// code string happens to appear in descriptive text
String[] items = list.contents();
for (String item : items) {
assertTrue(item.toLowerCase().contains(code));
}
}
@Test
public void filterByNameFragment() throws Exception {
showDialog();
JListFixture list = windowFixture.list();
final int FILTER_STRING_LEN = 5;
String filterStr = null;
while (filterStr == null) {
String code = getRandomCode();
String desc = FACTORY.getDescriptionText(
JCRSChooser.DEFAULT_AUTHORITY + ":" + code).toString();
// double check the text is suitable for filtering
if (desc != null && desc.length() >= FILTER_STRING_LEN) {
filterStr = desc.substring(0, FILTER_STRING_LEN).toLowerCase();
}
}
// Filter on the first few characters
windowFixture.textBox().enterText(filterStr);
windowFixture.robot.waitForIdle();
// Check list contents
for (String item : list.contents()) {
assertTrue(item.toLowerCase().contains(filterStr));
}
}
@Test
public void selectInListAndClickOKButton() throws Exception {
Future<CoordinateReferenceSystem> future = showDialog();
final int index = rand.nextInt(CODES.size());
windowFixture.list().clickItem(index);
windowFixture.robot.waitForIdle();
getButton("OK").click();
windowFixture.robot.waitForIdle();
CoordinateReferenceSystem crs = retrieveCRS(future);
assertNotNull(crs);
// Compare return value to list selection
CoordinateReferenceSystem expected = FACTORY.createCoordinateReferenceSystem(
JCRSChooser.DEFAULT_AUTHORITY + ":" + CODES.get(index));
assertTrue(CRS.equalsIgnoreMetadata(expected, crs));
}
/**
* Launches the dialog in a new thread.
*
* @return the Future for the dialog task
*/
private Future<CoordinateReferenceSystem> showDialog() throws Exception {
return showDialog(null);
}
/**
* Launches the dialog in a new thread.
*
* @param title custom title (may be {@code null}
*
* @return the Future for the dialog task
*/
private Future<CoordinateReferenceSystem> showDialog(final String title) throws Exception {
Future<CoordinateReferenceSystem> future = executor.submit(
new Callable<CoordinateReferenceSystem>() {
@Override
public CoordinateReferenceSystem call() throws Exception {
if (title == null) {
return JCRSChooser.showDialog();
} else {
return JCRSChooser.showDialog(title);
}
}
});
assertComponentDisplayed(JCRSChooser.CRSDialog.class);
windowFixture = listener.getFixture(DISPLAY_TIMEOUT);
return future;
}
/**
* Randomly chooses a code from the CODES list.
*
* @return the selected code
*/
private String getRandomCode() {
int index = rand.nextInt(CODES.size());
return CODES.get(index);
}
/**
* Retrieves a CRS from the given task future. If the value cannot be retrieved
* within the set time-out, an assertion error is raised.
*
* @param future task future
* @return the CRS
*/
private CoordinateReferenceSystem retrieveCRS(Future<CoordinateReferenceSystem> future)
throws Exception {
CoordinateReferenceSystem crs = null;
try {
crs = future.get(RETURN_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (TimeoutException ex) {
fail("Value not returned within max waiting time");
}
return crs;
}
}