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

package org.jdesktop.swingx.table;

import static org.hamcrest.CoreMatchers.is;
import static org.jdesktop.test.matchers.Matchers.property;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import java.beans.PropertyChangeEvent;
import java.util.logging.Logger;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import org.jdesktop.swingx.InteractiveTestCase;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.event.TableColumnModelExtListener;
import org.jdesktop.swingx.test.ColumnModelReport;
import org.jdesktop.test.TestUtils;
import org.jdesktop.test.matchers.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;


/**
 * Skeleton to unit test DefaultTableColumnExt.
 * 
 * Incomplete list of issues to test: 
 *   fired added after setVisible(true)
 *   behaviour when adding/removing invisible columns
 *   selection state
 * 
 * 
 * @author  Jeanette Winzenburg
 */
@RunWith(JUnit4.class)
public class TableColumnModelTest extends InteractiveTestCase {
    @SuppressWarnings("all")
    private static final Logger LOG = Logger
            .getLogger(TableColumnModelTest.class.getName());
    protected static final int COLUMN_COUNT = 3;

    /**
     * Issue 1423 - remove column must not change its visibility state
     */
    @Test
    public void testRemoveHidden() {
        TableColumnModelExt model = createColumnModel(4);
        TableColumnExt column = model.getColumnExt(0);
        column.setVisible(false);
        model.removeColumn(column);
        assertEquals(false, column.isVisible());
    }
   

    /**
     * Issue #1340-swingx: DefaultTableColumnModelExt must fire columnRemoved for hidden columns.
     */
    @Test
    public void testDefaultTableColumnModelExtRemoveHiddenColumns() {
        DefaultTableColumnModelExt columnModel = (DefaultTableColumnModelExt) new JXTable(10, 2).getColumnModel();
        TableColumnExt column = columnModel.getColumnExt(0);
        column.setVisible(false);
        ColumnModelReport report = new ColumnModelReport(columnModel);
        columnModel.removeColumn(column);
        assertTrue("columnModel must fire removed for hidden columns", report.hasRemovedEvent());
    }
    
    

    /**
     * added api to get array of ext listeners
     */
    @Test
    public void testGetTableColumnModelExtListener() {
        // any tableColumnModelExtListener will do
        JXTable table = new JXTable();
        DefaultTableColumnModelExt columnModel = (DefaultTableColumnModelExt) createColumnModel(COLUMN_COUNT);
        columnModel.addColumnModelListener(table);
        TableColumnModelExtListener[] listeners = columnModel.getTableColumnModelExtListeners();
        assertEquals(1, listeners.length);
        assertEquals(table, listeners[0]);
        
    }

    /**
     * Issue #369-swingx: properties of hidden columns are not fired. <p>
     * test the change from visible to hidden.
     */
    @Test
    public void testHideTableColumnPropertyNotification() {
        TableColumnModelExt columnModel = createColumnModel(COLUMN_COUNT);
        ColumnModelReport report = new ColumnModelReport();
        columnModel.addColumnModelListener(report);
        columnModel.getColumnExt(0).setVisible(false);
        TestUtils.assertPropertyChangeEvent(report.getPropertyChangeReport(), 
                "visible", true, false);
    }


    /**
     * Issue #846-swingx
     */
    @Test
    public void testIsAddedFromInvisibleTrue() {
        final DefaultTableColumnModelExt columnModel = (DefaultTableColumnModelExt) createColumnModel(3);
        TableColumnExt columnB = columnModel.getColumnExt(1);
        columnB.setVisible(false);
        // can't use report: the isRemovedToInvisible is valid during notification only
        TableColumnModelListener report = new TableColumnModelListener() {

            public void columnAdded(TableColumnModelEvent e) {
                int fromIndex = e.getToIndex();
                assertEquals("old column really removed", true, 
                        columnModel.isAddedFromInvisibleEvent(fromIndex));
                // note: the toIndex here is always the last
                // following are moves to position before hiding
            }
            public void columnRemoved(TableColumnModelEvent e) {}
            public void columnMarginChanged(ChangeEvent e) {  }
            public void columnMoved(TableColumnModelEvent e) {}
            public void columnSelectionChanged(ListSelectionEvent e) {}
            
        };
        columnModel.addColumnModelListener(report);
        columnB.setVisible(true);
    }

    /**
     * Issue #1123-swingx: isRemovedToInvisibleEvent incorrect on second (and subsequent)
     *   hiding of first visible column
     */
    @Test
    public void testIsRemovedToInvisibleTrue1122() {
        DefaultTableColumnModelExt columnModel = 
            (DefaultTableColumnModelExt) createColumnModel(3);
        // hide the first - okay
        columnModel.getColumnExt(0).setVisible(false);
        // wire listener
        assertIsRemovedToInvisible(columnModel, 0, true);
        // hide the first (== old second)
        columnModel.getColumnExt(0).setVisible(false);
    }


    /**
     * Issue #846-swingx
     */
    @Test
    public void testIsRemovedToInvisibleFalse() {
        DefaultTableColumnModelExt columnModel = (DefaultTableColumnModelExt) createColumnModel(3);
        // hide a column (paranoid: might create left-over-state)
        columnModel.getColumnExt(0).setVisible(false);
        // wire in-process listener
        assertIsRemovedToInvisible(columnModel, 0, false);
        // remove first visible column
        columnModel.removeColumn(columnModel.getColumnExt(0));
    }
    
    /**
     * Issue #846-swingx
     */
    @Test
    public void testIsRemovedToInvisibleTrue() {
        DefaultTableColumnModelExt columnModel = 
            (DefaultTableColumnModelExt) createColumnModel(3);
        assertIsRemovedToInvisible(columnModel, 2, true);
        // hide last column
        columnModel.getColumnExt(2).setVisible(false);
    }

    /**
     * Wire a TableColumnModelListener to test isRemovedToInvisible.
     * Note: can't use ColumnModelReport because valid only during notification.
     * 
     * @param columnModel the model to listen to        
     * @param index the index in the event to test
     * @param expected the expected result of isRemovedToInvisible
     */
    private void assertIsRemovedToInvisible(
            final DefaultTableColumnModelExt columnModel, final int index,
            final boolean expected) {
        // can't use report: the isRemovedToInvisible is valid during notification only
        TableColumnModelListener report = new TableColumnModelListener() {


            public void columnRemoved(TableColumnModelEvent e) {
                int fromIndex = e.getFromIndex();
                assertEquals("old visible index of removed", index, fromIndex);
                assertEquals("moved to invisible", expected, 
                        columnModel.isRemovedToInvisibleEvent(fromIndex));
                
            }
            public void columnAdded(TableColumnModelEvent e) {}
            public void columnMarginChanged(ChangeEvent e) {  }
            public void columnMoved(TableColumnModelEvent e) {}
            public void columnSelectionChanged(ListSelectionEvent e) {}
            
        };
        columnModel.addColumnModelListener(report);
    }

    
    /**
     * Issue #369-swingx: properties of hidden columns are not fired. <p>
     * test the change from hidden to visible.
     */
    @Test
    public void testShowTableColumnPropertyNotification() {
        TableColumnModelExt columnModel = createColumnModel(COLUMN_COUNT);
        Object identifier = "0";
        // sanity...
        assertNotNull(columnModel.getColumnExt(identifier));
        columnModel.getColumnExt(identifier).setVisible(false);
        
        TableColumnModelExtListener l = mock(TableColumnModelExtListener.class);
        columnModel.addColumnModelListener(l);
        columnModel.getColumnExt(identifier).setVisible(true);
        
        verify(l).columnPropertyChange(argThat(is(property("visible", false, true))));
    }
    

    /**
     * Issue #369-swingx: properties of hidden columns are not fired. <p>
     * test property changes while hidden 
     *
     */
    @Test
    public void testHiddenTableColumnPropertyNotification() {
        TableColumnModelExt columnModel = createColumnModel(COLUMN_COUNT);
        String identifier = "0";
        TableColumnExt columnExt = columnModel.getColumnExt(identifier);
        columnExt.setVisible(false);
        // sanity...
        assertNotNull(columnExt);
        String title = columnExt.getTitle() + "changed";
        ColumnModelReport report = new ColumnModelReport();
        columnModel.addColumnModelListener(report);
        columnExt.setIdentifier(title);
        TestUtils.assertPropertyChangeEvent(report.getPropertyChangeReport(), 
                "identifier", identifier, title);
    }
    
    /**
     * Issue #253-swingx: hiding/showing columns changes column sequence.
     * 
     * The test is modelled after the example code as 
     * http://forums.java.net/jive/thread.jspa?threadID=7344.
     *
     */
    @Test
    public void testHideShowColumns() {
        DefaultTableColumnModelExt model = (DefaultTableColumnModelExt) createColumnModel(10);   
        int[] columnsToHide = new int[] { 4, 7, 6, 8, };
        for (int i = 0; i < columnsToHide.length; i++) {
            model.getColumnExt(String.valueOf(columnsToHide[i])).setVisible(false);
        }
        // sanity: actually hidden
        assertEquals(model.getColumnCount(true) - columnsToHide.length, model.getColumnCount());
        for (int i = 0; i < columnsToHide.length; i++) {
            model.getColumnExt(String.valueOf(columnsToHide[i])).setVisible(true);
        }
        // sanity: all visible again
        assertEquals(10, model.getColumnCount());
        for (int i = 0; i < model.getColumnCount(); i++) {
            // the original sequence
            assertEquals(i, model.getColumn(i).getModelIndex());
        }
    
    }
    
    /**
     * test sequence of visible columns after hide/move/show.
     * 
     * Expected behaviour should be like in Thunderbird.
     *
     */
    @Test
    public void testMoveColumns() {
        DefaultTableColumnModelExt model = (DefaultTableColumnModelExt) createColumnModel(COLUMN_COUNT);
        TableColumnExt columnExt = model.getColumnExt(1);
        columnExt.setVisible(false);
        model.moveColumn(1, 0);
        columnExt.setVisible(true);
        assertEquals(columnExt.getModelIndex(), model.getColumnExt(2).getModelIndex());
    }
    /**
     * test the columnPropertyChangeEvent is fired as expected.
     *
     */
    @Test
    public void testColumnPropertyChangeNotification() {
        DefaultTableColumnModelExt model = (DefaultTableColumnModelExt) createColumnModel(COLUMN_COUNT);
        ColumnModelReport report = new ColumnModelReport();
        model.addColumnModelListener(report);
        TableColumn column = model.getColumn(0);
        column.setHeaderValue("somevalue");
        assertEquals(1, report.getColumnPropertyEventCount());
        PropertyChangeEvent event = report.getLastColumnPropertyEvent();
        assertEquals(column, event.getSource());
        assertEquals("headerValue", event.getPropertyName());
        assertEquals("somevalue", event.getNewValue());
    }
    /**
     * added TableColumnModelExtListener: test for add/remove extended listeners.
     *
     */
    @Test
    public void testAddExtListener() {
        DefaultTableColumnModelExt model = (DefaultTableColumnModelExt) createColumnModel(COLUMN_COUNT);
        ColumnModelReport extListener = new ColumnModelReport();
        model.addColumnModelListener(extListener);
        // JW: getListeners returns the count of exactly the given class?
//        assertEquals(1, model.getListeners(TableColumnModelExtListener.class).length);
//        assertEquals(2, model.getListeners(EventListener.class).length);
//        model.removeColumnModelListener(extListener);
//        assertEquals(0, model.getListeners(TableColumnModelExtListener.class).length);
//        assertEquals(0, model.getListeners(EventListener.class).length);
        assertEquals(1, model.getEventListenerList().getListenerCount(TableColumnModelExtListener.class));
        assertEquals(2, model.getEventListenerList().getListenerCount());
        model.removeColumnModelListener(extListener);
        assertEquals(0, model.getEventListenerList().getListenerCount(TableColumnModelExtListener.class));
        assertEquals(0, model.getEventListenerList().getListenerCount());
        
    }
    /**
     * Issue #??-swingx: incorrect isRemovedToInvisible after
     * removing an invisible column. 
     *
     */
    @Test
    public void testRemoveInvisibleColumn() {
        DefaultTableColumnModelExt model = (DefaultTableColumnModelExt) createColumnModel(COLUMN_COUNT);
        TableColumnExt tableColumnExt = ((TableColumnExt) model.getColumn(0));
        tableColumnExt.setVisible(false);
        model.removeColumn(tableColumnExt);
        assertEquals("visible column count must be reduced", COLUMN_COUNT - 1, model.getColumns(false).size());
        assertEquals("all columns count must be unchanged", COLUMN_COUNT - 1, model.getColumns(true).size());
        assertFalse("removing invisible must update event cache", model.isRemovedToInvisibleEvent(0));
    }

    
    @Test
    public void testGetColumns() {
        TableColumnModelExt model = createColumnModel(COLUMN_COUNT);
        ((TableColumnExt) model.getColumn(0)).setVisible(false);
        assertEquals("visible column count must be reduced", COLUMN_COUNT - 1, model.getColumns(false).size());
        assertEquals("all columns count must be unchanged", COLUMN_COUNT, model.getColumns(true).size());
    }
    /**
     * column count must be changed on changing 
     * column visibility.
     *
     */
    @Test
    public void testColumnCountOnSetInvisible() {
        TableColumnModel model = createColumnModel(COLUMN_COUNT);
        int columnCount = model.getColumnCount();
        TableColumnExt column = (TableColumnExt) model.getColumn(columnCount - 1);
        assertTrue(column.isVisible());
        column.setVisible(false);
        assertEquals("columnCount must be decremented", columnCount - 1, model.getColumnCount());
    }
    
    /**
     * Issue #156: must update internal state after setting invisible.
     * Here: the cached totalWidth. Expect similar inconsistency
     * with selection.
     *
     */
    @Test
    public void testTotalColumnWidth() {
        TableColumnModel model = createColumnModel(COLUMN_COUNT);
        int totalWidth = model.getTotalColumnWidth();
        TableColumnExt column = (TableColumnExt) model.getColumn(0);
        int columnWidth = column.getWidth();
        column.setVisible(false);
        assertEquals("new total width must be old minus invisible column width " + columnWidth,
                totalWidth - columnWidth, model.getTotalColumnWidth());
        
    }
    
    /** 
     * Issue #157: must fire columnRemoved after setting to invisible.
     *
     */
    @Test
    public void testRemovedFired() {
        TableColumnModel model = createColumnModel(COLUMN_COUNT);
        ColumnModelReport l = new ColumnModelReport();
        model.addColumnModelListener(l);
        TableColumnExt column = (TableColumnExt) model.getColumn(0);
        column.setVisible(false);
        assertTrue("must have fired columnRemoved", l.hasRemovedEvent());
    }

    /** 
     * Issue #157: must fire columnAdded after setting to invisible.
     *
     */
    @Test
    public void testAddedFired() {
        TableColumnModel model = createColumnModel(COLUMN_COUNT);
        ColumnModelReport l = new ColumnModelReport();
        TableColumnExt column = (TableColumnExt) model.getColumn(0);
        column.setVisible(false);
        model.addColumnModelListener(l);
        column.setVisible(true);
        assertTrue("must have fired columnRemoved", l.hasAddedEvent());
    }

    /**
     * columnAdded: event.getToIndex must be valid columnIndex.
     * 
     * 
     */
    @Test
     public void testAddInvisibleColumn()  {
         TableColumnModel model = createColumnModel(COLUMN_COUNT);
         TableColumnModelListener l = new TableColumnModelListener() {

            public void columnAdded(TableColumnModelEvent e) {
                assertTrue("toIndex must be positive", e.getToIndex() >= 0);
                ((TableColumnModel) e.getSource()).getColumn(e.getToIndex());
            }

            public void columnRemoved(TableColumnModelEvent e) {
                // TODO Auto-generated method stub
                
            }

            public void columnMoved(TableColumnModelEvent e) {
                // TODO Auto-generated method stub
                
            }

            public void columnMarginChanged(ChangeEvent e) {
                // TODO Auto-generated method stub
                
            }

            public void columnSelectionChanged(ListSelectionEvent e) {
                // TODO Auto-generated method stub
                
            }
             
         };
         model.addColumnModelListener(l);
         // add invisible column
         TableColumnExt invisibleColumn = new TableColumnExt(0);
         invisibleColumn.setVisible(false);
         model.addColumn(invisibleColumn);
         // sanity check: add visible column
         model.addColumn(createTableColumnExt(0));
    }
    /**
     * columnAt must work on visible columns.
     *
     */
    @Test
    public void testColumnAt() {
        TableColumnModel model = createColumnModel(COLUMN_COUNT);
        int totalWidth = model.getTotalColumnWidth();
        int lastColumn = model.getColumnIndexAtX(totalWidth - 10);
        assertEquals("lastColumn detected", model.getColumnCount() - 1, lastColumn);
        TableColumnExt column = (TableColumnExt) model.getColumn(lastColumn);
        column.setVisible(false);
        assertEquals("out of range", -1, model.getColumnIndexAtX(totalWidth - 10));
    }

//------------------  factory methods
    
    /**
     * creates and returns a TableColumnModelExt with the given number
     * of configured columns of type <code>TableColumnExt</code>.
     * 
     * @param columns the number of columns to create and add to the model
     * @return a <code>TableColumnModelExt</code> filled with columns.
     *    
     * @see createTableColumnExt
     */
    protected TableColumnModelExt createColumnModel(int columns) {
        TableColumnModelExt model = new DefaultTableColumnModelExt();
        for (int i = 0; i < columns; i++) {
            model.addColumn(createTableColumnExt(i));
      
        }
        return model;
    }

    /**
     * Creates and returns a TableColumnExt with simple standard configuration.
     * 
     * <pre><code>
     * column.getModelIndex() == modelIndex
     * column.getIdentifier() == String.valueOf(modelIndex);
     * </code></pre>
     *  
     * @param modelIndex the model column index to use for config
     * @return a <code>TableColumnExt</code> with standard configuration
     */
    protected TableColumnExt createTableColumnExt(int modelIndex) {
        TableColumnExt column = new TableColumnExt(modelIndex);
        column.setIdentifier(String.valueOf(modelIndex));
        return column;
    }
    

}
