swingx-master/swingx-core/src/test/java/org/jdesktop/swingx/table/ColumnControlButtonTest.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 java.awt.Component;
import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;

import org.jdesktop.swingx.InteractiveTestCase;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.action.AbstractActionExt;
import org.jdesktop.swingx.action.ActionContainerFactory;
import org.jdesktop.swingx.icon.EmptyIcon;
import org.jdesktop.swingx.plaf.ColumnControlButtonAddon;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;
import org.jdesktop.swingx.table.ColumnControlButton.ColumnVisibilityAction;
import org.jdesktop.swingx.table.ColumnControlButton.DefaultColumnControlPopup;
import org.jdesktop.swingx.table.ColumnControlButtonVisualCheck.GroupKeyActionGrouper;
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;

import static org.junit.Assert.*;


/**
 * @author Jeanette Winzenburg
 */
@RunWith(JUnit4.class)
public class ColumnControlButtonTest extends InteractiveTestCase {
    private static final Logger LOG = Logger
            .getLogger(ColumnControlButtonTest.class.getName());
    
    protected TableModel sortableTableModel;
    
    @Before
    public void setUpJ4() throws Exception {
        setUp();
    }
    
    @After
    public void tearDownJ4() throws Exception {
        tearDown();
    }
    
    /**
     * Issue: add support for custom grouping of additional actions 
     * http://java.net/jira/browse/SWINGX-968
     */
    @Test
    public void testAdditonalActionGrouping() {
        JXTable table = new JXTable(10, 4);
        AbstractActionExt custom = new AbstractActionExt("Custom") {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO Auto-generated method stub
                
            }
        };
        custom.putValue(GroupKeyActionGrouper.GROUP_KEY, 0);
        table.getActionMap().put(ColumnControlButton.COLUMN_CONTROL_MARKER + "myCommand", custom);
        ColumnControlButton button = new ColumnControlButton(table);
        button.setActionGrouper(new GroupKeyActionGrouper());
        DefaultColumnControlPopup popup = (DefaultColumnControlPopup) button.getColumnControlPopup();
        assertEquals("additional actions visible, component count expected ", 
                table.getColumnCount() 
                    + 1 /* separator */ + 3 /*default actions with column. prefix*/
                    + 1 /* separator custom group */ + 1 /* custom action */, 
                popup.getPopupMenu().getComponentCount());
    }

    /**
     * Issue http://java.net/jira/browse/SWINGX-1466: add control to show/hide
     *   additional actions
     */
    @Test
    public void testAdditionalActionVisible() {
        JXTable table = new JXTable(10, 4);
        ColumnControlButton button = new ColumnControlButton(table);
        assertEquals(true, button.getAdditionalActionsVisible());
        DefaultColumnControlPopup popup = (DefaultColumnControlPopup) button.getColumnControlPopup();
        assertEquals("additional actions visible, component count expected ", 
                table.getColumnCount() + 1 /* separator */ + 3 /* actions with column. prefix*/, 
                popup.getPopupMenu().getComponentCount());
        PropertyChangeReport report = new PropertyChangeReport(button);
        button.setAdditionalActionsVisible(false);
        TestUtils.assertPropertyChangeEvent(report, "additionalActionsVisible", true, false);
        assertEquals(false, button.getAdditionalActionsVisible());
        assertEquals("additional actions hidden, component count expected ", 
                table.getColumnCount(),
                popup.getPopupMenu().getComponentCount());
        
    }
    
    /**
     * Issue #1573-swingx: !hideable column action must be disabled
     * Problem: isn't initially
     */
    @Test
    public void testColumnVisibilityActionOnHideableInitial() {
        JXTable table = new JXTable(10, 3);
        // set the first column !hideable
        table.getColumnExt(0).setHideable(false);
        table.setColumnControlVisible(true);
        ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
        ColumnVisibilityAction action = columnControl.getColumnVisibilityActions().get(0);
        assertFalse("action must be disabled initially", action.isEnabled());
    }
    
    @Test
    public void testColumnVisibilityActionOnHideable() {
        JXTable table = new JXTable(10, 3);
        table.setColumnControlVisible(true);
        ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
        ColumnVisibilityAction action = columnControl.getColumnVisibilityActions().get(0);
        TableColumnExt columnExt = table.getColumnExt(0);
        // visible property is false
        columnExt.setVisible(false);
        columnExt.setHideable(false);
        assertTrue("visibility action must be selected if not hideable", action.isSelected());
        assertFalse("action must be disabled", action.isEnabled());
        columnExt.setHideable(true);
        assertFalse("visibility action must be unselected if hideable", action.isSelected());
    }

    /**
     * Issue #404-swingx: load column control margin from ui.
     * Test that column control configures itself with the icon from the ui.
     */
    @Test
    public void testColumnControlInitialUpdateInsetsUIResource() {
      ColumnControlButton control = new ColumnControlButton(new JXTable());
      // PENDING JW: why not same? insets can be shared - or not?
      // probably setMargin interferes - is doing some things ... 
      assertEquals("columnControl must have margin from ui", 
                UIManager.getInsets(ColumnControlButton.COLUMN_CONTROL_BUTTON_MARGIN_KEY),
                control.getMargin());
    }

    /**
     * Issue #404-swingx: load column control margin from ui.
     * Test that column control loads the margin.
     */
    @SuppressWarnings("unused")
    @Test
    public void testColumnControlLoadsMargin() {
        // force loading by instantiating a column control
      ColumnControlButton control = new ColumnControlButton(new JXTable());
        assertNotNull("columnControl must load lf-specific icon", 
                UIManager.getInsets(ColumnControlButton.COLUMN_CONTROL_BUTTON_MARGIN_KEY));
    }

    /**
     * Issue #404-swingx: load column control margin from ui.
     * Test that addon loads the margin.
     */
    @Test
    public void testColumnControlAddonLoadsMargin() {
        // direct loading of addon
        LookAndFeelAddons.contribute(new ColumnControlButtonAddon());
        assertNotNull("addon must load lf-specific icon", 
                UIManager.getInsets(ColumnControlButton.COLUMN_CONTROL_BUTTON_MARGIN_KEY));
    }
    
    
    /**
     * Issue #404-swingx: load column control icon from ui.
     * Test that table instantiates the column control with ui icon.
     */
    @Test
    public void testColumnControlInXTable() {
      JXTable table = new JXTable();
      ColumnControlButton control = (ColumnControlButton) table.getColumnControl();
      assertSame("columnControl must have icon from ui", 
                UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY),
                control.getIcon());
    }
    
    /**
     * Issue #404-swingx: load column control icon from ui.
     * Test that icon is not updated on updateUI if not uiResource
     */
    @Test
    public void testColumnControlIconNotUpdateNonActionUIResource() {
        ColumnControlButton control = new ColumnControlButton(new JXTable(), new EmptyIcon());
        Icon icon = control.getIcon();
        String lf = UIManager.getLookAndFeel().getName();
        setSystemLF(!defaultToSystemLF);
        if (lf.equals(UIManager.getLookAndFeel().getName())) {
            LOG.info("cannot run layoutOnLFChange - equal LF" + lf);
            return;
        }
        SwingUtilities.updateComponentTreeUI(control);
        assertSame("icon must not be updated on LF change if not UIResource: ", 
                icon, control.getIcon());
    }

    /**
     * Issue #404-swingx: load column control icon from ui.
     * Test that is updated on updateUI
     */
    @Test
    public void testColumnControlIconUpdateActionUIResource() {
        ColumnControlButton control = new ColumnControlButton(new JXTable());
        Icon icon = control.getIcon();
        String lf = UIManager.getLookAndFeel().getName();
        setSystemLF(!defaultToSystemLF);
        if (lf.equals(UIManager.getLookAndFeel().getName())) {
            LOG.info("cannot run layoutOnLFChange - equal LF" + lf);
            return;
        }
        SwingUtilities.updateComponentTreeUI(control);
        assertNotSame("sanity: ui did reload icon: ", icon, 
                UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY));

        assertNotSame("icon must be updated on LF change: ", icon, control.getIcon());

    }
    /**
     * Issue #404-swingx: load column control icon from ui.
     * Test that column control configures itself with the icon from the ui.
     */
    @Test
    public void testColumnControlInitialUpdateActionUIResource() {
      ColumnControlButton control = new ColumnControlButton(new JXTable());
      assertSame("columnControl must have icon from ui", 
                UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY),
                control.getIcon());
    }

    /**
     * Issue #404-swingx: load column control icon from ui.
     * Test that column control loads the icon.
     */
    @SuppressWarnings("unused")
    @Test
    public void testColumnControlLoadsIcon() {
        // force loading by instantiating a column control
      ColumnControlButton control = new ColumnControlButton(new JXTable());
        assertNotNull("columnControl must load lf-specific icon", 
                UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY));
    }

    /**
     * Issue #404-swingx: load column control icon from ui.
     * Test that addon loads the icon.
     */
    @Test
    public void testColumnControlAddonLoadsIcon() {
        // direct loading of addon
        LookAndFeelAddons.contribute(new ColumnControlButtonAddon());
        assertNotNull("addon must load lf-specific icon", 
                UIManager.getIcon(ColumnControlButton.COLUMN_CONTROL_BUTTON_ICON_KEY));
    }
    
    /**
     * Issue #429-swingx: ClassCastException if column identifiers are not
     * String type.
     *
     */
    @Test
    public void testNonStringIdentifier() {
        JXTable table = new JXTable(0, 2);
        table.getColumn(0).setIdentifier(new Object());
        table.setColumnControlVisible(true);
        table.getColumnControl();
    }
    
    @Test
    public void testNotNullColumnModelListener() {
        JXTable table = new JXTable(0, 2);
        table.setColumnControlVisible(true);
        assertNotNull(((ColumnControlButton)table.getColumnControl()).columnModelListener);
    }
    /**
     * Tests if subclasses are allowed to not create a visibility action.
     * This might happen if they want to exempt certain columns from 
     * user interaction.
     *
     */
    @Test
    public void testNullVisibilityAction() {
        JXTable table = new JXTable();
        ColumnControlButton columnControl = new ColumnControlButton(table) {

                    @Override
                    protected ColumnVisibilityAction createColumnVisibilityAction(TableColumn column) {
                        if (column.getModelIndex() == 0) return null;
                        return super.createColumnVisibilityAction(column);
                    }
            
            
        };
        table.setColumnControl(columnControl);
        table.setColumnControlVisible(true);
        table.setModel(sortableTableModel);
    }
    /**
     * test that the actions synch's its own selected property with 
     * the column's visible property. <p>
     * 
     * Looks as if the non-synch of action.setSelected only shows 
     * if the ColumnControlPopup doesn't create a menuitem via ActionFactory: the
     * listeners internally installed via ActionFactory probably take care?
     *  <p>
     * 
     * An analogous test in the incubator (in kleopatra/.../table) did fail
     * for a dialog based custom ColumnControlPopup. For now, changed the visibility
     * action to explicitly update the tableColumn. All tests are passing,
     * but need to further evaluate.
     *
     */
    @Test
    public void testColumnVisibilityAction() {
        JXTable table = new JXTable(10, 3);
        table.setColumnControlVisible(true);
        ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
        ColumnVisibilityAction action = columnControl.getColumnVisibilityActions().get(0);
        TableColumnExt columnExt = table.getColumnExt(0);
        boolean visible = columnExt.isVisible();
        // sanity
        assertTrue(visible);
        assertEquals(columnExt.isVisible(), action.isSelected());
        action.setSelected(!visible);
        // hmmm... here it's working? unexpected
        // synch might be done by the listener's installed by ActionFactor.createMenuItem()?
        assertEquals(!visible, columnExt.isVisible());
    }
 
    /**
     * Tests that enabled property of table and column control is synched dynamically.
     */
    @Test
    public void testDynamicDisabled() {
        JXTable table = new JXTable(10, 3);
        table.setColumnControlVisible(true);
        assertEquals(table.isEnabled(), table.getColumnControl().isEnabled());
        table.setEnabled(!table.isEnabled());
        assertEquals(table.isEnabled(), table.getColumnControl().isEnabled());
    }

    /**
     * suspected: enabled not synched on init. 
     * But is (done in ccb.installTable()). 
     *
     */
    @Test
    public void testInitialDisabled() {
        JXTable table = new JXTable(10, 3);
        table.setEnabled(false);
        table.setColumnControlVisible(true);
        assertEquals(table.isEnabled(), table.getColumnControl().isEnabled());
    }


    /**
     * guarantee that at least one column is always visible.
     *
     */
    @Test
    public void testMinimumColumnCountOne() {
        JXTable table = new JXTable(10, 2);
        table.setColumnControlVisible(true);
        table.getColumnExt(0).setVisible(false);
        assertEquals(1, table.getColumnCount());
    }
    
    /**
     * Issue #229-swingx: increasing listener list in column actions.
     * 
     */
    @Test
    public void testActionListenerCount() {
        JXTable table = new JXTable(10, 1);
        Action action = table.getActionMap().get(JXTable.HORIZONTALSCROLL_ACTION_COMMAND);
        if (!(action instanceof AbstractActionExt)) {
            LOG.info("cannot run testColumnActionListenerCount - action not of type AbstractAction");
            return;
        }
        AbstractActionExt extAction = (AbstractActionExt) action;
        assertTrue(extAction.isStateAction());
        assertEquals(0, extAction.getPropertyChangeListeners().length);
        AbstractButton menuItem = new JCheckBoxMenuItem();
        ActionContainerFactory factory = new ActionContainerFactory(null);
        factory.configureSelectableButton(menuItem, extAction, null);
        // sanity: here the action is bound to a menu item in the columnControl
        // should have one ad
        int initialPCLCount = extAction.getPropertyChangeListeners().length;
        // sanity: expect it to be 2 - one is the menuitem itself, another 
        // the TogglePCL registered by the ActionContainerFacory
        assertEquals(2, initialPCLCount);
        menuItem = new JToggleButton();
        factory.configureSelectableButton(menuItem, extAction, null);
        // 2 menuitems are listening
       assertEquals(2* initialPCLCount, extAction.getPropertyChangeListeners().length);
        
    }
    

    /**
     * Issue #153-swingx: ClassCastException if actionMap key is not a string.
     *
     */
    @Test
    public void testNonStringActionKeys() {
        JXTable table = new JXTable();
        Action l = new AbstractAction("dummy") {

            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO Auto-generated method stub
                
            }
            
        };
        table.registerKeyboardAction(l , KeyStroke.getKeyStroke("ESCAPE"), JComponent.WHEN_FOCUSED);
        table.setColumnControlVisible(true);
        table.getColumnControl();
    }
    
    @Test
    public void testColumnControlReleaseAction() {
        final JXTable table = new JXTable(sortableTableModel);
        final TableColumnExt priorityColumn = table.getColumnExt("First Name");
        int listenerCount = priorityColumn.getPropertyChangeListeners().length;
        table.setColumnControlVisible(true);
        // JW: the columnControlButton is created lazily, so we
        // have to access to test if listeners are registered.
        table.getColumnControl();
        assertEquals("numbers of listeners must be increased", listenerCount + 1, 
                priorityColumn.getPropertyChangeListeners().length);
        int totalColumnCount = table.getColumnCount();
        table.removeColumn(priorityColumn);
        assertEquals("number of columns reduced", totalColumnCount - 1, table.getColumnCount());
        assertEquals("all listeners must be removed", 0, 
                priorityColumn.getPropertyChangeListeners().length);
     }

    /**
     * Issue #212-swingx: 
     * 
     * Behaviour change: 
     * <ul>
     * <li>before - guarantee that exactly one column is always visible, 
     *    independent of source of visibiblity change
     * <li> now - this is true for user gesture induced invisible (through 
     *    columnControl, but not for programmatic hiding. It's up to 
     *    developers to not hide all. To alleviate the effects if they 
     *    hide all, the JXTableHeader and ColumnControl is always visible.
     * </ul>
     * This is testing the old behaviour (guarding against regression)
     * Here we directly set the second last visible column to invisible. This 
     * failed if a) column visibility is set after adding the table to a frame
     * and b) model.count = 2.
     *
     */
    @Test
    public void testSetAllColumnsToInvisible() {
        // This test will not work in a headless configuration.
        if (GraphicsEnvironment.isHeadless()) {
            return;
        }
        final JXTable table = new JXTable(10, 2);
        table.setColumnControlVisible(true);
        wrapWithScrollingInFrame(table, "");
        table.getColumnExt(0).setVisible(false);
        assertEquals(1, table.getColumnCount());
        table.getColumnExt(0).setVisible(false);
        assertEquals(0, table.getColumnCount());
    }
    /**
     * Issue #212-swingx: 
     * 
     * guarantee that exactly one column is always visible if 
     * visibility is toggled via the ColumnControl.
     * 
     * Here we deselect the menuitem.
     * 
     */
    @Test
    public void testSetLastColumnMenuItemToUnselected() {
        // This test will not work in a headless configuration.
        if (GraphicsEnvironment.isHeadless()) {
            return;
        }
        final JXTable table = new JXTable(10, 1);
        table.setColumnControlVisible(true);
        wrapWithScrollingInFrame(table, "");
        ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
        Component[] items = ((DefaultColumnControlPopup) columnControl.getColumnControlPopup()).getPopupMenu().getComponents();
        ((JMenuItem) items[0]).setSelected(false);
        assertEquals(1, table.getColumnCount());
    }

   /** 
    * Issue #192: initially invisibility columns are hidden
    * but marked as visible in control.
    *
    * Issue #38 (swingx): initially invisble columns don't show up
    * in the column control list.
    * 
    * 
    */
    @Test
   public void testColumnControlInvisibleColumns() {
       final JXTable table = new JXTable(sortableTableModel);
       // columns set to invisible before setting the columnControl
       // will not be inserted into the column control's list
//     table.getColumnExt("Last Name").setVisible(false);
       table.setColumnControlVisible(true);
       int totalColumnCount = table.getColumnCount();
       final TableColumnExt priorityColumn = table.getColumnExt("First Name");
       priorityColumn.setVisible(false);
       ColumnControlButton columnControl = (ColumnControlButton) table.getColumnControl();
       assertNotNull("popup menu not null", columnControl.popup);
       int columnMenuItems = 0;
       Component[] items = ((DefaultColumnControlPopup) columnControl.getColumnControlPopup()).getPopupMenu().getComponents();
       for (int i = 0; i < items.length; i++) {
           if (!(items[i] instanceof JMenuItem)) {
               break;
           }
           columnMenuItems++;
       }
       // wrong assumption - has separator and actions!
       assertEquals("menu items must be equal to columns", totalColumnCount, 
               columnMenuItems);
       JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) ((DefaultColumnControlPopup) columnControl.getColumnControlPopup()).getPopupMenu()
           .getComponent(0);
       // sanit assert
       assertEquals(priorityColumn.getHeaderValue(), menuItem.getText());
       assertEquals("selection of menu must be equal to column visibility", 
               priorityColumn.isVisible(), menuItem.isSelected());
   }

    public ColumnControlButtonTest() {
        super("ColumnControlButtonTest");
    }
    // flag used in setup to explicitly choose LF
    private boolean defaultToSystemLF;

    
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        // make sure we have the same default for each test
        defaultToSystemLF = false;
        setSystemLF(defaultToSystemLF);
         sortableTableModel = new AncientSwingTeam();
     }

    public static void main(String args[]) {
       setSystemLF(false);
      ColumnControlButtonTest test = new ColumnControlButtonTest();
      try {
        test.runInteractiveTests();
      //    test.runInteractiveTests("interactive.*Column.*");
//          test.runInteractiveTests("interactive.*TableHeader.*");
      //    test.runInteractiveTests("interactive.*SorterP.*");
      //    test.runInteractiveTests("interactive.*Column.*");
      } catch (Exception e) {
          System.err.println("exception when executing interactive tests:");
          e.printStackTrace();
      }
  }
    
}
