swingx-master/swingx-core/src/test/java/org/jdesktop/swingx/JXListTest.java
/*
 * $Id$
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 */
package org.jdesktop.swingx;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GraphicsEnvironment;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.Collator;
import java.util.Arrays;
import java.util.Vector;
import java.util.logging.Logger;
import java.util.regex.Pattern;

import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.RowFilter;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.text.Position.Bias;

import org.jdesktop.swingx.JXList.DelegatingRenderer;
import org.jdesktop.swingx.decorator.ColorHighlighter;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.Highlighter;
import org.jdesktop.swingx.decorator.SearchPredicate;
import org.jdesktop.swingx.decorator.ComponentAdapterTest.JXListT;
import org.jdesktop.swingx.hyperlink.LinkModel;
import org.jdesktop.swingx.renderer.DefaultListRenderer;
import org.jdesktop.swingx.renderer.StringValue;
import org.jdesktop.swingx.renderer.StringValues;
import org.jdesktop.swingx.rollover.ListRolloverController;
import org.jdesktop.swingx.rollover.RolloverProducer;
import org.jdesktop.swingx.sort.DefaultSortController;
import org.jdesktop.swingx.sort.ListSortController;
import org.jdesktop.swingx.sort.RowFilters;
import org.jdesktop.swingx.sort.StringValueRegistry;
import org.jdesktop.swingx.sort.TableSortController;
import org.jdesktop.test.AncientSwingTeam;
import org.jdesktop.test.PropertyChangeReport;
import org.jdesktop.test.TestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;


/**
 * Testing JXList. <p>
 * 
 * Note: all tests related to the disabled sorting/filtering support are moved to
 * JXListSortRevamp and forced to fail.
 * 
 * @author Jeanette Winzenburg
 */
@RunWith(JUnit4.class)
public class JXListTest extends InteractiveTestCase {

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(JXListTest.class
            .getName());
    
    protected ListModel listModel;
    protected DefaultListModel ascendingListModel;
    /** empty default list */
    private JXList list;
    private StringValue sv;

    
    /**
     * Issue #1563-swingx: find cell that was clicked for componentPopup
     * 
     * Test api and event firing.
     */
    @Test
    public void testPopupTriggerLocationAvailable() {
        JXList table = new JXList(listModel);
        MouseEvent event = new MouseEvent(table, 0,
                0, 0, 40, 5, 0, false);
        PropertyChangeReport report = new PropertyChangeReport(table);
        table.getPopupLocation(event);
        assertEquals(event.getPoint(), table.getPopupTriggerLocation());
        TestUtils.assertPropertyChangeEvent(report, "popupTriggerLocation", 
                null, event.getPoint());
    }
    
    
    /**
     * Issue #1563-swingx: find cell that was clicked for componentPopup
     * 
     * Test safe return value.
     */
    @Test
    public void testPopupTriggerCopy() {
        JXList table = new JXList(listModel);
        MouseEvent event = new MouseEvent(table, 0,
                0, 0, 40, 5, 0, false);
        table.getPopupLocation(event);
        assertNotSame("trigger point must not be same", 
                table.getPopupTriggerLocation(), table.getPopupTriggerLocation());
    }
    
    /**
     * Issue #1563-swingx: find cell that was clicked for componentPopup
     * 
     * Test safe handle null.
     */
    @Test
    public void testPopupTriggerKeyboard() {
        JXList table = new JXList(listModel);
        MouseEvent event = new MouseEvent(table, 0,
                0, 0, 40, 5, 0, false);
        table.getPopupLocation(event);
        PropertyChangeReport report = new PropertyChangeReport(table);
        table.getPopupLocation(null);
        assertNull("trigger must null", 
                table.getPopupTriggerLocation());
        TestUtils.assertPropertyChangeEvent(report, "popupTriggerLocation", 
                event.getPoint(), null);
    }


    /**
     * Issue #1263-swingx: JXList selectedValues must convert index to model.
     */
    @Test
    public void testGetSelectedValue() {
        DefaultListModel model = new DefaultListModel();
        model.addElement("One");
        model.addElement("Two");
        model.addElement("Three");
        model.addElement("Four");
        model.addElement("Five");
        model.addElement("Six");
        model.addElement("Seven");
        JXList list = new JXList();
        list.setAutoCreateRowSorter(true);
        list.setModel(model);
        list.setSelectedIndex(2);
        assertEquals("Three", list.getSelectedValue());
        list.setRowFilter(new RowFilter<ListModel, Integer>() {

            @Override
            public boolean include(Entry<? extends ListModel, ? extends Integer> entry) {
                return entry.getStringValue(entry.getIdentifier()).contains("e");
            }

        });
        assertEquals("Three", list.getSelectedValue());
    }

    /**
     * Issue #1263-swingx: JXList selectedValues must convert index to model.
     */
    @Test
    public void testGetSelectedValues() {
        DefaultListModel model = new DefaultListModel();
        model.addElement("One");
        model.addElement("Two");
        model.addElement("Three");
        model.addElement("Four");
        model.addElement("Five");
        model.addElement("Six");
        model.addElement("Seven");
        JXList list = new JXList();
        list.setAutoCreateRowSorter(true);
        list.setModel(model);
        list.setSelectedIndex(2);
        list.addSelectionInterval(0, 2);
        assertTrue(Arrays.deepEquals(new Object[] {"One", "Two", "Three"}, list.getSelectedValues()));
        list.setRowFilter(new RowFilter<ListModel, Integer>() {

            @Override
            public boolean include(Entry<? extends ListModel, ? extends Integer> entry) {
                return entry.getStringValue(entry.getIdentifier()).contains("e");
            }

        });
        assertTrue(Arrays.deepEquals(new Object[] {"One", "Three"}, list.getSelectedValues()));
        list.clearSelection();
        list.addSelectionInterval(0, 2);
        assertTrue(Arrays.deepEquals(new Object[] {"One", "Three", "Five"}, list.getSelectedValues()));
    }

    /**
     * Issue #1263-swingx: JXList selectedValues must convert index to model.
     */
    @Test
    public void testSetSelectedValue() {
        DefaultListModel model = new DefaultListModel();
        model.addElement("One");
        model.addElement("Two");
        model.addElement("Three");
        model.addElement("Four");
        model.addElement("Five");
        model.addElement("Six");
        model.addElement("Seven");
        JXList list = new JXList();
        list.setAutoCreateRowSorter(true);
        list.setModel(model);
        list.setSelectedValue("Three", false);
        assertEquals(2, list.getSelectedIndex());
        list.setRowFilter(new RowFilter<ListModel, Integer>() {

            @Override
            public boolean include(Entry<? extends ListModel, ? extends Integer> entry) {
                return entry.getStringValue(entry.getIdentifier()).contains("e");
            }

        });
        assertEquals(1, list.getSelectedIndex());
        list.setSelectedValue("Five", false);
        assertEquals(2, list.getSelectedIndex());
    }


    /**
     * Issue #1232-swingx: JXList must fire property change on setCellRenderer.
     * 
     */
    @Test
    public void testRendererNotification() {
        JXList list = new JXList();
        assertNotNull("sanity: ", list.getCellRenderer());
        // very first setting: fires twice ... a bit annoying but ... waiting for complaints ;-)
        list.setCellRenderer(new DefaultListRenderer());
        PropertyChangeReport report = new PropertyChangeReport(list);
        list.setCellRenderer(new DefaultListRenderer());
        TestUtils.assertPropertyChangeEvent(report, "cellRenderer", null, list.getCellRenderer());
    }
    /**
     * Issue #1162-swingx: getNextMatch incorrect if sorted.
     */
    @Test
    public void testNextMatch() {
        JXList list = new JXList(AncientSwingTeam.createNamedColorListModel(), true);
        int index = list.getNextMatch("b", 0, Bias.Forward);
        assertEquals(1, index);
        list.toggleSortOrder();
        assertEquals(0, list.getNextMatch("b", 0, Bias.Forward));
    }
    
    /**
     * Issue #1162-swingx: getNextMatch incorrect for non-standard stringValue
     */
    @Test
    public void testNextMatchUseString() {
        JXList list = new JXList(AncientSwingTeam.createNamedColorListModel(), true);
        list.setCellRenderer(new DefaultListRenderer(sv));
        assertEquals("must not find a match for 'b', all start with 'r'", 
                -1, list.getNextMatch("b", 0, Bias.Forward));
    }
    /**
     * Issue 1161-swingx: JXList not completely updated on setRowFilter
     */
    @Test
    public void testRevalidateOnSetRowFilter() throws InterruptedException, InvocationTargetException {
        // This test will not work in a headless configuration.
        if (GraphicsEnvironment.isHeadless()) {
            LOG.fine("cannot run ui test - headless environment");
            return;
        }
        
        final JXList list = new JXList(AncientSwingTeam.createNamedColorListModel(), true);
        showWithScrollingInFrame(list, "");
        final Dimension size = list.getSize();
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                RowFilter<? super ListModel, ? super Integer> filter = RowFilters.regexFilter(Pattern.CASE_INSENSITIVE, "^b");
                list.setRowFilter(filter);
            }
        });
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                // subtraction arbitrary number, don't want to depend on single pixels
                assertTrue("height must be adjusted to reduced number of rows, " +
                		"but was (old/current): " + size.height + "/" + list.getSize().height, 
                		size.height - 50 > list.getSize().height);

            }
        });
    }
//--------------- string rep
    /**
     * Issue #1152-swingx: re-enable filtering with single-string-representation.
     * was: Issue #767-swingx: consistent string representation.
     * 
     * Here: test Pattern filtering uses string rep
     */
    @Test
    public void testListGetStringUsedInPatternFilter() {
        JXList list = new JXList(AncientSwingTeam.createNamedColorListModel(), true);
        list.setCellRenderer(new DefaultListRenderer(sv));
        RowFilter<Object, Integer> filter = RowFilter.regexFilter("R/G/B: -2.*", 0);
        list.setRowFilter(filter);
        assertTrue(list.getElementCount() > 0);
        assertEquals(sv.getString(list.getElementAt(0)), list.getStringAt(0));
    }

    /**
     * Issue #1152-swingx: re-enable filtering with single-string-representation.
     * was: Issue #767-swingx: consistent string representation.
     * 
     * Here: test list has stringValueProvider and configures the sortController with it
     */
    @Test
    public void testStringValueRegistry() {
        JXList list = new JXList(AncientSwingTeam.createNamedColorListModel(), true);
        assertSame(list.getStringValueRegistry(), getSortController(list).getStringValueProvider());
    }
    
    /**
     * Issue #1152-swingx: re-enable filtering with single-string-representation.
     * was: Issue #767-swingx: consistent string representation.
     * 
     * Here: test updates the sortController on renderer change.
     */
    @Test
    public void testStringValueRegistryFromRendererChange() {
        JXList list = new JXList(AncientSwingTeam.createNamedColorListModel(), true);
        StringValueRegistry provider = list.getStringValueRegistry();
        list.setCellRenderer(new DefaultListRenderer(sv));
        assertEquals(list.getWrappedCellRenderer(), provider.getStringValue(0, 0));
    }

    /**
     * Issue #1152-swingx: re-enable filtering with single-string-representation.
     * was: Issue #767-swingx: consistent string representation.
     * 
     * Here: test getStringAt use provider (sanity, trying to pull the rag failed
     * during re-enable)
     */
    @Test
    public void testStringAtUseProvider() {
        JXList list = new JXList(AncientSwingTeam.createNamedColorListModel(), true);
        list.setCellRenderer(new DefaultListRenderer(sv));
        list.getStringValueRegistry().setStringValue(StringValues.TO_STRING, 0);
        assertEquals(StringValues.TO_STRING.getString(list.getElementAt(0)),
                list.getStringAt(0));
        
    }
    /**
     * Issue #1152-swingx: re-enable filtering with single-string-representation.
     * was: Issue #767-swingx: consistent string representation.
     * 
     * Here: test getStringAt of ComponentAdapter use provider 
     * (sanity, trying to pull the rag failed during re-enable)
     */
    @Test
    public void testStringAtComponentAdapterUseProvider() {
        JXListT list = new JXListT(AncientSwingTeam.createNamedColorListModel(), true);
        list.setCellRenderer(new DefaultListRenderer(sv));
        list.getStringValueRegistry().setStringValue(StringValues.TO_STRING, 0);
        ComponentAdapter adapter = list.getComponentAdapter();
        assertEquals(StringValues.TO_STRING.getString(list.getElementAt(0)),
                adapter.getStringAt(0, 0));
        
    }
    
//----------------- sorter api on JXList

    /**
     * JXList has responsibility to guarantee usage of 
     * its comparator.
     */
    @Test
    public void testSetComparatorToSortController() {
        JXList list = new JXList(listModel, true);
        list.setComparator(Collator.getInstance());
        assertSame(list.getComparator(), getSortController(list).getComparator(0));
    }
    
    /**
     * added xtable.setSortOrder(int, SortOrder)
     * 
     */
    @Test
    public void testSetSortOrder() {
        JXList list = new JXList(ascendingListModel, true);
        list.setSortOrder(SortOrder.ASCENDING);
        assertSame("column must be sorted after setting sortOrder on ", SortOrder.ASCENDING, list.getSortOrder());
        assertSame(SortOrder.ASCENDING, getSortController(list).getSortOrder(0));
    }
    


    /**
     * testing new sorter api: 
     * getSortOrder(), toggleSortOrder(), resetSortOrder().
     *
     */
    @Test
    public void testToggleSortOrder() {
        JXList list = new JXList(ascendingListModel, true);
        assertSame(SortOrder.UNSORTED, list.getSortOrder());
        list.toggleSortOrder();
        assertSame(SortOrder.ASCENDING, list.getSortOrder());
        list.toggleSortOrder();
        assertSame(SortOrder.DESCENDING, list.getSortOrder());
        list.resetSortOrder();
        assertSame(SortOrder.UNSORTED, list.getSortOrder());
    }

    /**
     * prepare sort testing: internal probs with SortController?
     */
    @Test
    public void testSortController() {
        JXList list = new JXList(ascendingListModel, true);
        assertNotNull("sortController must be initialized", list.getRowSorter());
    }
    

//----------------- data api on JXList
    /**
     * testing that rowSorter's model is updated
     */
    @Test
    public void testSetModel() {
        JXList list = new JXList(true);
        list.setModel(listModel);
        assertEquals(listModel.getSize(), list.getElementCount());
        assertSame(listModel, list.getRowSorter().getModel());
    }



    @Test(expected = IndexOutOfBoundsException.class)
    public void testConvertToModelPreconditions() {
        final JXList list = new JXList(ascendingListModel, true);
        assertEquals(20, list.getElementCount());
        RowFilter<ListModel, Integer> filter = RowFilters.regexFilter("0", 0);
        list.setRowFilter(filter);
        assertEquals(2, list.getElementCount());
        list.convertIndexToModel(list.getElementCount());
    }
 

    @Test(expected = IndexOutOfBoundsException.class)
    public void testElementAtPreconditions() {
        final JXList list = new JXList(ascendingListModel, true);
        assertEquals(20, list.getElementCount());
        RowFilter<ListModel, Integer> filter = RowFilters.regexFilter("0", 0);
        list.setRowFilter(filter);
        assertEquals(2, list.getElementCount());
        list.getElementAt(list.getElementCount());
    }

    /**
     * 
     */
    @Test(expected = IndexOutOfBoundsException.class)
    public void testConvertToViewPreconditions() {
        final JXList list = new JXList(ascendingListModel);
        list.setAutoCreateRowSorter(true);
        assertEquals(20, list.getElementCount());
        RowFilter<ListModel, Integer> filter = RowFilters.regexFilter("0", 0);
        list.setRowFilter(filter);
        assertEquals(2, list.getElementCount());
        list.convertIndexToView(ascendingListModel.getSize());
    }

    @Test
    public void testNoSorter() {
        JXList list = new JXList(ascendingListModel);
        assertEquals(ascendingListModel.getSize(), list.getElementCount());
        assertEquals(ascendingListModel.getElementAt(0), list.getElementAt(0));
    }
    
    @Test
    public void testSorterNotSorted() {
        JXList list = new JXList(ascendingListModel, true);
        assertEquals(ascendingListModel.getSize(), list.getElementCount());
        assertEquals(ascendingListModel.getElementAt(0), list.getElementAt(0));
    }
    
    @Test
    public void testSorterSorted() {
        JXList list = new JXList(ascendingListModel, true);
        list.setSortOrder(SortOrder.DESCENDING);
        assertEquals(ascendingListModel.getSize(), list.getElementCount());
        assertEquals(ascendingListModel.getElementAt(0), list.getElementAt(list.getElementCount() - 1));
    }


    //------------- sort properties

    @Test
    public void testPropertiesToSorterOnSetRowsorter() {
        list.setAutoCreateRowSorter(true);
        list.setSortsOnUpdates(false);
        list.setSortable(false);
        Collator comparator = Collator.getInstance();
        list.setComparator(comparator);
        ListSortController<?> controller = new ListSortController<ListModel>(list.getModel());
        list.setRowSorter(controller);
        assertEquals("sortable propagated", false, controller.isSortable(0));
        assertSame("comparator propagated", comparator, controller.getComparator(0));
        assertEquals("sortsOnUpdates propagated", false, controller.getSortsOnUpdates());
    }
    
    @Test
    public void testSortsOnUpdate() {
        PropertyChangeReport report = new PropertyChangeReport(list);
        list.setSortsOnUpdates(false);
        TestUtils.assertPropertyChangeEvent(report, "sortsOnUpdates", true, false);
        assertFalse(list.getSortsOnUpdates());
    }

    
    @Test
    public void testSortable() {
        PropertyChangeReport report = new PropertyChangeReport(list);
        list.setSortable(false);
        TestUtils.assertPropertyChangeEvent(report, "sortable", true, false);
        assertFalse(list.isSortable());
    }
    
    /**
     * Setting table's sortable property updates controller.
     */
    @Test
    public void testTableRowFilterSynchedToController() {
        JXList list = new JXList(true);
        RowFilter<Object, Object> filter = RowFilters.regexFilter(".*");
        list.setRowFilter(filter);
        assertEquals(filter, getSortController(list).getRowFilter());
        assertEquals(filter, list.getRowFilter());
    }
    
    /**
     * Setting table's sortable property updates controller.
     */
    @Test
    public void testSortOrderCycle() {
        JXList list = new JXList(true);
        SortOrder[] cycle = new SortOrder[] {SortOrder.DESCENDING, SortOrder.UNSORTED};
        PropertyChangeReport report = new PropertyChangeReport(list);
        list.setSortOrderCycle(cycle);
        TestUtils.assertPropertyChangeEvent(report, "sortOrderCycle", 
                    DefaultSortController.getDefaultSortOrderCycle(), list.getSortOrderCycle());
    }


    /**
     * Convenience: type cast of default rowSorter.
     * @param list
     * @return
     */
    private ListSortController<? extends ListModel> getSortController(JXList list) {
        return (ListSortController<? extends ListModel>) list.getRowSorter();
    }

    //------------ rowSorter api
    
    /**
     * test filterEnabled property on initialization.
     *
     */
    @Test
    public void testConstructorAutoCreateSorter() {
        assertAutoCreateRowSorter(new JXList(), false);
        assertAutoCreateRowSorter(new JXList(new DefaultListModel()), false);
        assertAutoCreateRowSorter(new JXList(new Vector<Object>()), false);
        assertAutoCreateRowSorter(new JXList(new Object[] { }), false);
        
        assertAutoCreateRowSorter(new JXList(false), false);
        assertAutoCreateRowSorter(new JXList(new DefaultListModel(), false), false);
        assertAutoCreateRowSorter(new JXList(new Vector<Object>(), false), false);
        assertAutoCreateRowSorter(new JXList(new Object[] { }, false), false);

        assertAutoCreateRowSorter(new JXList(true), true);
        assertAutoCreateRowSorter(new JXList(new DefaultListModel(), true), true);
        assertAutoCreateRowSorter(new JXList(new Vector<Object>(), true), true);
        assertAutoCreateRowSorter(new JXList(new Object[] { }, true), true);
    }
    
    private void assertAutoCreateRowSorter(JXList list, boolean b) {
        assertEquals(b, list.getAutoCreateRowSorter());
    }
    
   
    @Test
    public void testRowSorterSet() {
        assertNull(list.getRowSorter());
        ListSortController<ListModel> controller = new ListSortController<ListModel>(list.getModel());
        PropertyChangeReport report = new PropertyChangeReport(list);
        list.setRowSorter(controller);
        TestUtils.assertPropertyChangeEvent(report, list, "rowSorter", null, controller);
        assertSame(controller, list.getRowSorter());
    }
    
    @Test
    public void testAutoCreateRowSorterSet() {
        PropertyChangeReport report = new PropertyChangeReport(list);
        list.setAutoCreateRowSorter(true);
        assertTrue(list.getAutoCreateRowSorter());
        TestUtils.assertPropertyChangeEvent(report, "autoCreateRowSorter", false, true, false);
        assertNotNull(list.getRowSorter());
    }
 
    
    /**
     * Test assumptions of accessing list model/view values through
     * the list's componentAdapter.
     */
    @Test
    public void testComponentAdapterCoordinates() {
        JXList list = new JXList(ascendingListModel, true);
        list.setComparator(TableSortController.COMPARABLE_COMPARATOR);
        Object originalFirstRowValue = list.getElementAt(0);
        Object originalLastRowValue = list.getElementAt(list.getElementCount() - 1);
        assertEquals("view row coordinate equals model row coordinate", 
                list.getModel().getElementAt(0), originalFirstRowValue);
        // sort first column - actually does not change anything order 
        list.toggleSortOrder();
        // sanity asssert
        assertEquals("view order must be unchanged ", 
                list.getElementAt(0), originalFirstRowValue);
        // invert sort
        list.toggleSortOrder();
        // sanity assert
        assertEquals("view order must be reversed changed ", 
                list.getElementAt(0), originalLastRowValue);
        ComponentAdapter adapter = list.getComponentAdapter();
        assertEquals("adapter filteredValue expects row view coordinates", 
                list.getElementAt(0), adapter.getFilteredValueAt(0, 0));
        // adapter coordinates are view coordinates
        adapter.row = 0;
        adapter.column = 0;
        assertEquals("adapter.getValue must return value at adapter coordinates", 
                list.getElementAt(0), adapter.getValue());
        assertEquals("adapter.getValue must return value at adapter coordinates", 
                list.getElementAt(0), adapter.getValue(0));
    }
    

//------------------------end of re-enable sort/filter
    /**
     * Issue #816-swingx: Delegating renderer must create list's default.
     * Consistent api: expose wrappedRenderer the same way as wrappedModel
     */
    @Test
    public void testWrappedRendererDefault() {
        JXList list = new JXList();
        DelegatingRenderer renderer = (DelegatingRenderer) list.getCellRenderer();
        assertSame("wrapping renderer must use list's default on null", 
                 renderer.getDelegateRenderer(), list.getWrappedCellRenderer());
    }

    /**
     * Issue #816-swingx: Delegating renderer must create list's default.
     * Consistent api: expose wrappedRenderer the same way as wrappedModel
     */
    @Test
    public void testWrappedRendererCustom() {
        JXList list = new JXList();
        DelegatingRenderer renderer = (DelegatingRenderer) list.getCellRenderer();
        ListCellRenderer custom = new DefaultListRenderer();
        list.setCellRenderer(custom);
        assertSame("wrapping renderer must use list's default on null", 
                 renderer.getDelegateRenderer(), list.getWrappedCellRenderer());
    }
    
    /**
     * Issue #816-swingx: Delegating renderer must create list's default.
     * Delegating uses default on null, here: default default.
     */
    @Test
    public void testDelegatingRendererUseDefaultSetNull() {
        JXList list = new JXList();
        ListCellRenderer defaultRenderer = list.createDefaultCellRenderer();
        DelegatingRenderer renderer = (DelegatingRenderer) list.getCellRenderer();
        list.setCellRenderer(null);
        assertEquals("wrapping renderer must use list's default on null", 
                defaultRenderer.getClass(), renderer.getDelegateRenderer().getClass());
    }

    /**
     * Issue #816-swingx: Delegating renderer must create list's default.
     * Delegating has default from list initially, here: default default.
     */
    @Test
    public void testDelegatingRendererUseDefault() {
        JXList list = new JXList();
        ListCellRenderer defaultRenderer = list.createDefaultCellRenderer();
        assertEquals("sanity: creates default", DefaultListRenderer.class, 
                defaultRenderer.getClass());
        DelegatingRenderer renderer = (DelegatingRenderer) list.getCellRenderer();
        assertEquals(defaultRenderer.getClass(), renderer.getDelegateRenderer().getClass());
    }
    
    /**
     * Issue #816-swingx: Delegating renderer must create list's default.
     * Delegating has default from list initially, here: custom default.
     */
    @Test
    public void testDelegatingRendererUseCustomDefaultSetNull() {
        JXList list = new JXList() {

            @Override
            protected ListCellRenderer createDefaultCellRenderer() {
                return new CustomDefaultRenderer();
            }
            
        };
        ListCellRenderer defaultRenderer = list.createDefaultCellRenderer();
        DelegatingRenderer renderer = (DelegatingRenderer) list.getCellRenderer();
        list.setCellRenderer(null);
        assertEquals("wrapping renderer must use list's default on null",
                defaultRenderer.getClass(), renderer.getDelegateRenderer().getClass());
    }
    
    /**
     * Issue #816-swingx: Delegating renderer must create list's default.
     * Delegating has default from list initially, here: custom default.
     */
    @Test
    public void testDelegatingRendererUseCustomDefault() {
        JXList list = new JXList() {

            @Override
            protected ListCellRenderer createDefaultCellRenderer() {
                return new CustomDefaultRenderer();
            }
            
        };
        ListCellRenderer defaultRenderer = list.createDefaultCellRenderer();
        assertEquals("sanity: creates custom", CustomDefaultRenderer.class, 
                defaultRenderer.getClass());
        DelegatingRenderer renderer = (DelegatingRenderer) list.getCellRenderer();
        assertEquals(defaultRenderer.getClass(), renderer.getDelegateRenderer().getClass());
    }
    /**
     * Dummy extension for testing - does nothing more as super.
     */
    public static class CustomDefaultRenderer extends DefaultListCellRenderer {
    }
    
    /**
     * Issue #767-swingx: consistent string representation.
     * 
     * Here: test api on JXTable.
     */
    @Test
    public void testGetString() {
        JXList list = new JXList(AncientSwingTeam.createNamedColorListModel());
        StringValue sv = new StringValue() {

            public String getString(Object value) {
                if (value instanceof Color) {
                    Color color = (Color) value;
                    return "R/G/B: " + color.getRGB();
                }
                return StringValues.TO_STRING.getString(value);
            }
            
        };
        list.setCellRenderer(new DefaultListRenderer(sv));
        String text = list.getStringAt(0);
        assertEquals(sv.getString(list.getElementAt(0)), text);
    }
    

    /**
     * test that swingx renderer is used by default.
     *
     */
    @Test
    public void testDefaultListRenderer() {
        JXList list = new JXList();
        ListCellRenderer renderer = ((DelegatingRenderer) list.getCellRenderer()).getDelegateRenderer();
        assertTrue("default renderer expected to be DefaultListRenderer " +
                        "\n but is " + renderer.getClass(),
                renderer instanceof DefaultListRenderer);
    }
    
    /**
     * Issue #473-swingx: NPE in list with highlighter. <p> 
     * 
     * Renderers are doc'ed to cope with invalid input values.
     * Highlighters can rely on valid ComponentAdapter state. 
     * JXList delegatingRenderer is the culprit which does set
     * invalid ComponentAdapter state. Negative invalid index.
     *
     */
    @Test
    public void testIllegalNegativeListRowIndex() {
        JXList list = new JXList(new Object[] {1, 2, 3});
        ListCellRenderer renderer = list.getCellRenderer();
        renderer.getListCellRendererComponent(list, "dummy", -1, false, false);
        SearchPredicate predicate = new SearchPredicate("\\QNode\\E");
        Highlighter searchHighlighter = new ColorHighlighter(predicate, null, Color.RED);
        list.addHighlighter(searchHighlighter);
        renderer.getListCellRendererComponent(list, "dummy", -1, false, false);
    }
    
    /**
     * Issue #473-swingx: NPE in list with highlighter. <p> 
     * 
     * Renderers are doc'ed to cope with invalid input values.
     * Highlighters can rely on valid ComponentAdapter state. 
     * JXList delegatingRenderer is the culprit which does set
     * invalid ComponentAdapter state. Invalid index > valid range.
     *
     */
    @Test
    public void testIllegalExceedingListRowIndex() {
        JXList list = new JXList(new Object[] {1, 2, 3});
        ListCellRenderer renderer = list.getCellRenderer();
        renderer.getListCellRendererComponent(list, "dummy", list.getElementCount(), false, false);
        SearchPredicate predicate = new SearchPredicate("\\QNode\\E");
        Highlighter searchHighlighter = new ColorHighlighter(predicate, null, Color.RED);
        list.addHighlighter(searchHighlighter);
        renderer.getListCellRendererComponent(list, "dummy", list.getElementCount(), false, false);
    }
    
    /**
     * test convenience method accessing the configured adapter.
     *
     */
    @Test
    public void testConfiguredComponentAdapter() {
        JXList list = new JXList(new Object[] {1, 2, 3});
        ComponentAdapter adapter = list.getComponentAdapter();
        assertEquals(0, adapter.column);
        assertEquals(0, adapter.row);
        adapter.row = 1;
        // corrupt adapter
        adapter.column = 1;
        adapter = list.getComponentAdapter(0);
        assertEquals(0, adapter.column);
        assertEquals(0, adapter.row);
    }
    

    /**
     * test exceptions on null data(model, vector, array).
     *
     */
    @Test
    public void testNullData() {
        try {
            new JXList((ListModel) null);
            fail("JXList contructor must throw on null data");
        } catch (IllegalArgumentException e) {
            // expected
        } catch (Exception e) {
            fail("unexpected exception type " + e);
        }
        
        try {
           new JXList((Vector<?>) null);
            fail("JXList contructor must throw on null data");
        } catch (IllegalArgumentException e) {
            // expected
        } catch (Exception e) {
            fail("unexpected exception type " + e);
        }
        
        try {
            new JXList((Object[]) null);
             fail("JXList contructor must throw on null data");
         } catch (IllegalArgumentException e) {
             // expected
         } catch (Exception e) {
             fail("unexpected exception type " + e);
         }
    }
    
    
    /**
     * add and test comparator property.
     * 
     */
    @Test
    public void testComparator() {
        JXList list = new JXList();
        assertNull(list.getComparator());
        Collator comparator = Collator.getInstance();
        PropertyChangeReport report = new PropertyChangeReport();
        list.addPropertyChangeListener(report);
        list.setComparator(comparator);
        assertEquals(comparator, list.getComparator());
        assertEquals(1, report.getEventCount());
        assertEquals(1, report.getEventCount("comparator"));
        
    }

    /**
     * test if LinkController/executeButtonAction is properly registered/unregistered on
     * setRolloverEnabled.
     *
     */
    @Test
    public void testLinkControllerListening() {
        JXList table = new JXList();
        table.setRolloverEnabled(true);
        assertNotNull("LinkController must be listening", getLinkControllerAsPropertyChangeListener(table, RolloverProducer.CLICKED_KEY));
        assertNotNull("LinkController must be listening", getLinkControllerAsPropertyChangeListener(table, RolloverProducer.ROLLOVER_KEY));
        assertNotNull("execute button action must be registered", table.getActionMap().get(JXList.EXECUTE_BUTTON_ACTIONCOMMAND));
        table.setRolloverEnabled(false);
        assertNull("LinkController must not be listening", getLinkControllerAsPropertyChangeListener(table, RolloverProducer.CLICKED_KEY ));
        assertNull("LinkController must be listening", getLinkControllerAsPropertyChangeListener(table, RolloverProducer.ROLLOVER_KEY));
        assertNull("execute button action must be de-registered", table.getActionMap().get(JXList.EXECUTE_BUTTON_ACTIONCOMMAND));
    }

    private PropertyChangeListener getLinkControllerAsPropertyChangeListener(JXList table, String propertyName) {
        PropertyChangeListener[] listeners = table.getPropertyChangeListeners(propertyName);
        for (int i = 0; i < listeners.length; i++) {
            if (listeners[i] instanceof ListRolloverController<?>) {
                return (ListRolloverController<?>) listeners[i];
            }
        }
        return null;
    }


    protected ListModel createListModel() {
        JXList list = new JXList();
        return new DefaultComboBoxModel(list.getActionMap().allKeys());
    }

    protected DefaultListModel createAscendingListModel(int startRow, int count) {
        DefaultListModel l = new DefaultListModel();
        for (int row = startRow; row < startRow  + count; row++) {
            l.addElement(new Integer(row));
        }
        return l;
    }
    protected DefaultListModel createListModelWithLinks() {
        DefaultListModel model = new DefaultListModel();
        for (int i = 0; i < 20; i++) {
            try {
                LinkModel link = new LinkModel("a link text " + i, null, new URL("http://some.dummy.url" + i));
                if (i == 1) {
                    URL url = JXEditorPaneTest.class.getResource("resources/test.html");

                    link = new LinkModel("a resource", null, url);
                }
                model.addElement(link);
            } catch (MalformedURLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        return model;
    }

    /**
     * Creates and returns a StringValue which maps a Color to it's R/G/B rep, 
     * prepending "R/G/B: "
     * 
     * @return the StringValue for color.
     */
    private StringValue createColorStringValue() {
        StringValue sv = new StringValue() {

            public String getString(Object value) {
                if (value instanceof Color) {
                    Color color = (Color) value;
                    return "R/G/B: " + color.getRGB();
                }
                return StringValues.TO_STRING.getString(value);
            }
            
        };
        return sv;
    }
    

    /**
     * Creates and returns a number filter, passing values which are numbers and
     * have int values inside or outside of the bounds (included), depending on the given 
     * flag.
     * 
     * @param lowerBound
     * @param upperBound
     * @param inside 
     * @return
     */
//    protected Filter createNumberFilter(final int lowerBound, final int upperBound, final boolean inside) {
//        PatternFilter f = new PatternFilter() {
//
//            @Override
//            public boolean test(int row) {
//                Object value = getInputValue(row, getColumnIndex());
//                if (!(value instanceof Number)) return false;
//                boolean isInside = ((Number) value).intValue() >= lowerBound 
//                    && ((Number) value).intValue() <= upperBound;
//                return inside ? isInside : !isInside;
//            }
//            
//        };
//        return f;
//    }
    
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        list = new JXList();
        listModel = createListModel();
        ascendingListModel = createAscendingListModel(0, 20);
        sv = createColorStringValue();
    }
    public JXListTest() {
        super("JXList Tests");
    }

    
    @Before
    public void setUpJ4() throws Exception {
        setUp();
    }
    
    @After
    public void tearDownJ4() throws Exception {
        tearDown();
    }


    
}
