swingx-master/swingx-core/src/test/java/org/jdesktop/swingx/plaf/basic/core/ListSortUITest.java
/*
 * $Id$
 *
 * Copyright 2009 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * 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; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
package org.jdesktop.swingx.plaf.basic.core;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;

import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.DefaultRowSorter;
import javax.swing.ListModel;
import javax.swing.RowFilter;
import javax.swing.SortOrder;
import javax.swing.RowFilter.Entry;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.jdesktop.swingx.InteractiveTestCase;
import org.jdesktop.swingx.JXEditorPaneTest;
import org.jdesktop.swingx.JXList;
import org.jdesktop.swingx.JXListSortRevamp.DefaultListModelF;
import org.jdesktop.swingx.hyperlink.LinkModel;
import org.jdesktop.swingx.plaf.basic.core.ListSortUI.ModelChange;
import org.jdesktop.swingx.sort.ListSortController;
import org.jdesktop.swingx.sort.RowFilters;
import org.jdesktop.swingx.sort.TableSortController;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;


/**
 * Tests for SortManager (mainly to understand).
 * 
 * @author Jeanette Winzenburg
 */
@RunWith(JUnit4.class)
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ListSortUITest extends InteractiveTestCase {
    @SuppressWarnings("unused")
    private static final Logger LOG = Logger.getLogger(ListSortUITest.class
            .getName());
    
    protected DefaultListModelF ascendingListModel;
    private JXList list;
    private ListSortController<ListModel> controller;
//    private ListSortUI sortUI;
    private int testRow;

    
    /**
     * Issue #1536-swingx: AIOOB on restoring selection with filter
     * Reopened: overfixed - the removeIndexInterval _does_ take 
     * the endIndex instead of length.
     * 
     */
    @Test
    public void testSelectionWithFilterXListRemove() {
        JXList table = new JXList(ascendingListModel, true);
        // set selection somewhere below the filtered (which is 0)
        int selected = 1;
        table.setSelectionInterval(selected, selected);
        // exclude rows based on identifier (here: first item
        final RowFilter filter = new RowFilters.GeneralFilter() {
            
            List excludes = Arrays.asList(0);
            @Override
            protected boolean include(
                    Entry<? extends Object, ? extends Object> entry,
                    int index) {
                return !excludes.contains(entry.getIdentifier());
            }
            
        };
        table.setRowFilter(filter);
        assertEquals("sanity: filtered selection", selected - 1, table.getSelectedIndex());
        // remove last row
        ascendingListModel.remove(ascendingListModel.getSize() - 1);
        assertEquals("filtered selection unchanged", selected - 1, table.getSelectedIndex());
        assertFalse(table.isSelectionEmpty());
    }
    /**
     * Issue #1536-swingx: AIOOB on restoring selection with filter
     * This is a core issue, sneaked into ListSortUI by c&p
     * 
     */
    @Test
    public void testSelectionWithFilterXListInsert() {
        DefaultListModel model = new DefaultListModel();
        // a model with 3 elements is the minimum size to demonstrate
        // the bug
        int last = 2;
        for (int i = 0; i <= last; i++) {
            model.addElement(i);
        }
        JXList table = new JXList(model, true);
        // set selection to the end
        table.setSelectionInterval(last, last);
        // exclude rows based on identifier
        final RowFilter filter = new RowFilters.GeneralFilter() {
            
            List excludes = Arrays.asList(0);
            @Override
            protected boolean include(
                    Entry<? extends Object, ? extends Object> entry,
                    int index) {
                return !excludes.contains(entry.getIdentifier());
            }
            
        };
        table.setRowFilter(filter);
        // insertRow _before or at_ selected model index, such that
        // endIndex (in event) > 1
        model.add(2, "x");
    }

    @Test(expected = IllegalStateException.class)
    public void testConstructorDifferentSorter() {
        new ListSortUI(new JXList(true), new ListSortController<ListModel>(ascendingListModel));
    }
    
    @Test(expected = NullPointerException.class)
    public void testConstructorNullSorter() {
        new ListSortUI(new JXList(), null);
    }
    
    @Test(expected = NullPointerException.class)
    public void testConstructorNullList() {
        new ListSortUI(null, new ListSortController<ListModel>(ascendingListModel));
    }
    
    @Test
    public void testSetFilterKeepsSelection() {
        int selection = 0;
        list.setSelectedIndex(selection);
        RowFilter<Object, Integer> filter = RowFilters.regexFilter(".*", 0);
        controller.setRowFilter(filter);
        assertEquals("setting filters must keep selection", selection, list.getSelectedIndex());
    }


    /**
     * Issue #223
     * test if selection is updated on add row above selection.
     *
     */
    @Test
    public void testAddRowAboveSelectionInvertedOrder() {
        // select the last row in view coordinates
        int selectedRow = list.getElementCount() - 2;
        list.setSelectedIndex(selectedRow);
        // revert order 
        list.setSortOrder(SortOrder.DESCENDING);
        assertEquals("second row must be selected", 1, list.getSelectedIndex());
        // add row in model coordinates
        // insert high value
        Object row = new Integer(100);
        ascendingListModel.addElement(row);
        assertEquals(row, list.getElementAt(0));
        // selection must be moved one below
        assertEquals("selection must be incremented by one ", 2, list.getSelectedIndex());
    }
    
    /**
     * Issue #855-swingx: throws AIOOB on repeated remove/add.
     * Reason is that the lead/anchor is not removed in removeIndexInterval
     */
    @Test
    public void testAddRemoveSelect() {
        list.setSortOrder(SortOrder.ASCENDING);
        list.setSelectedIndex(0);
        ascendingListModel.remove(0);
        assertTrue("sanity - empty selection after remove", list.isSelectionEmpty());
        ascendingListModel.addElement(-1);
        assertTrue("sanity - empty selection re-adding", list.isSelectionEmpty());
        list.setSelectedIndex(0);
    }
    
    
    /**
     * 
     * Issue #173-swingx.
     * 
     * table.setFilters() leads to selectionListener
     * notification while internal table state not yet stable.
     * 
     * example (second one, from Nicola):
     * http://www.javadesktop.org/forums/thread.jspa?messageID=117814
     *
     */
    @Test
    public void testSelectionListenerNotification() {
        assertEquals(20, list.getElementCount());
        final int modelRow = 0;
        // set a selection 
        list.setSelectedIndex(modelRow);
        ListSelectionListener l = new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent e) {
                if (e.getValueIsAdjusting()) return;
                int viewRow = list.getSelectedIndex(); 
                assertEquals("view index visible", 0, viewRow);
                // JW: the following checks if the reverse conversion succeeds
                list.convertIndexToModel(viewRow);
                
            }
            
        };
        list.getSelectionModel().addListSelectionListener(l);
        RowFilter<ListModel, Integer> filter = RowFilters.regexFilter("0", 0);
        ((ListSortController<? extends ListModel>) list.getRowSorter()).setRowFilter(filter);
        assertEquals(2, list.getElementCount());
    }


    @Test
    public void testSelectionAfterSort() {
        // use the 2 to be sure the comparable is used
        list.setSelectedIndex(testRow);
        list.setSortOrder(SortOrder.DESCENDING);
        int index = list.getSelectedIndex();
        assertEquals("last row must be selected after sorting", 
                ascendingListModel.getSize() - (testRow + 1) , index);
        
    }

    /**
     * Issue #477-swingx:
     * 
     * Selection must be cleared after setModel. This is from
     * super's contract.
     *
     */
    @Test
    public void testSetModelEmptySelection() {
        final JXList list = new JXList(new DefaultListModel(), true);
        int selection = 0;
        list.setSelectedIndex(selection);
        list.setModel(ascendingListModel);
        assertTrue("setting model must clear selectioon", list.isSelectionEmpty());
        assertEquals(ascendingListModel.getSize(), list.getElementCount());
    }
    /**
     * Selection must be cleared after dataChanged. 
     *
     */
    @Test
    public void testDataChangedEmptySelection() {
        final JXList list = new JXList(ascendingListModel, true);
        int selection = 0;
        list.setSelectedIndex(selection);
        ascendingListModel.fireContentsChanged();
        assertTrue("dataChanged must clear selection", list.isSelectionEmpty());
    }
    /**
     * Selection must be cleared after dataChanged. 
     *
     */
    @Test
    public void testDataChangedSortedEmptySelection() {
        final JXList list = new JXList(ascendingListModel, true);
        int selection = 0;
        list.setSelectedIndex(selection);
        list.setSortOrder(SortOrder.DESCENDING);
        ascendingListModel.fireContentsChanged();
        assertTrue("dataChanged must clear selection", list.isSelectionEmpty());
    }
    
    /**
     * test if selection is kept after deleting a row above the
     * selected.
     * 
     * This fails because the ui-delegate has its hands in removing
     * selection after removed, that is they are doubly removed.
     *
     */
    @Test
    public void testSelectionAfterAddAbove() {
        // selecte second row
        list.setSelectedIndex(1);
        // remove first 
        ascendingListModel.insertElementAt(5, 0);
        assertEquals("selected must have moved after adding at start", 
                2, list.getSelectedIndex());
    }
    
    /**
     * test if selection is kept after deleting a row above the
     * selected.
     * 
     * This fails because the ui-delegate has its hands in removing
     * selection after removed, that is they are doubly removed.
     *
     */
    @Test
    public void testSelectionAfterDeleteAbove() {
        // selecte second row
        list.setSelectedIndex(1);
        // remove first 
        ascendingListModel.remove(0);
        assertEquals("first row must be selected removing old first", 
                0, list.getSelectedIndex());
    }
    


//------------------- ModelChange - temporary ...    
    @Test
    public void testModelAdded() {
        int first = 3;
        int last = 5;
        ListDataEvent e = new ListDataEvent(new DefaultListModel(), ListDataEvent.INTERVAL_ADDED, last, first);
        ModelChange change = new ModelChange(e);
        assertEquals(ListDataEvent.INTERVAL_ADDED, change.type);
        assertFalse(change.allRowsChanged);
        assertEquals(last-first +1, change.length);
        assertEquals(first, change.startModelIndex);
        assertEquals(last, change.endModelIndex);
    }
    @Test
    public void testModelRemoved() {
        int first = 3;
        int last = 5;
        ListDataEvent e = new ListDataEvent(new DefaultListModel(), ListDataEvent.INTERVAL_REMOVED, last, first);
        ModelChange change = new ModelChange(e);
        assertEquals(ListDataEvent.INTERVAL_REMOVED, change.type);
        assertFalse(change.allRowsChanged);
        assertEquals(last-first +1, change.length);
        assertEquals(first, change.startModelIndex);
        assertEquals(last, change.endModelIndex);
    }
    @Test
    public void testModelChanged() {
        int first = 3;
        int last = 5;
        ListDataEvent e = new ListDataEvent(new DefaultListModel(), ListDataEvent.CONTENTS_CHANGED, last, first);
        ModelChange change = new ModelChange(e);
        assertEquals(ListDataEvent.CONTENTS_CHANGED, change.type);
        assertFalse(change.allRowsChanged);
        assertEquals(last-first +1, change.length);
        assertEquals(first, change.startModelIndex);
        assertEquals(last, change.endModelIndex);
    }
    @Test
    public void testModelAllChanged() {
        int first = -1;
        int last = -1;
        ListDataEvent e = new ListDataEvent(new DefaultListModel(), ListDataEvent.CONTENTS_CHANGED, last, first);
        ModelChange change = new ModelChange(e);
        assertEquals(ListDataEvent.CONTENTS_CHANGED, change.type);
        assertTrue(change.allRowsChanged);
        assertEquals(-1, change.startModelIndex);
        assertEquals(-1, change.endModelIndex);
        assertEquals(-1, change.length);
    }
    
  //-------------------- factory methods, setup    
    protected ListModel createListModel() {
        JXList list = new JXList();
        return new DefaultComboBoxModel(list.getActionMap().allKeys());
    }

    protected DefaultListModelF createAscendingListModel(int startRow, int count) {
        DefaultListModelF l = new DefaultListModelF();
        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;
    }

    
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        ascendingListModel = createAscendingListModel(0, 20);
        list = new JXList(ascendingListModel, true);
        controller = new ListSortController<ListModel>(list.getModel());
        list.setComparator(TableSortController.COMPARABLE_COMPARATOR);
        list.setRowSorter(controller);
        testRow = 2;
    }
    
    
    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
    }

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

}
