/* * $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.Component; import java.awt.Dimension; import java.awt.GraphicsEnvironment; import java.awt.KeyboardFocusManager; import java.awt.Point; import java.awt.event.MouseEvent; import java.beans.PropertyChangeListener; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.sql.Time; import java.sql.Timestamp; import java.text.Collator; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Set; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.swing.Action; import javax.swing.DefaultCellEditor; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.RowFilter; import javax.swing.RowSorter; import javax.swing.RowSorter.SortKey; import javax.swing.ScrollPaneLayout; import javax.swing.SortOrder; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.TableModelEvent; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; import org.jdesktop.swingx.JXTable.GenericEditor; import org.jdesktop.swingx.action.BoundAction; import org.jdesktop.swingx.decorator.AbstractHighlighter; import org.jdesktop.swingx.decorator.ColorHighlighter; import org.jdesktop.swingx.decorator.ComponentAdapter; import org.jdesktop.swingx.decorator.ComponentAdapterTest.JXTableT; import org.jdesktop.swingx.decorator.Highlighter; import org.jdesktop.swingx.decorator.PatternPredicate; import org.jdesktop.swingx.hyperlink.LinkModel; import org.jdesktop.swingx.renderer.DefaultTableRenderer; import org.jdesktop.swingx.renderer.HyperlinkProvider; import org.jdesktop.swingx.renderer.StringValue; import org.jdesktop.swingx.renderer.StringValues; import org.jdesktop.swingx.rollover.RolloverController; import org.jdesktop.swingx.rollover.RolloverProducer; import org.jdesktop.swingx.rollover.TableRolloverController; import org.jdesktop.swingx.sort.DefaultSortController; import org.jdesktop.swingx.sort.RowFilters; import org.jdesktop.swingx.sort.SortUtils; import org.jdesktop.swingx.sort.StringValueRegistry; import org.jdesktop.swingx.sort.TableSortController; import org.jdesktop.swingx.table.ColumnControlButton; import org.jdesktop.swingx.table.ColumnFactory; import org.jdesktop.swingx.table.DefaultTableColumnModelExt; import org.jdesktop.swingx.table.NumberEditorExt; import org.jdesktop.swingx.table.TableColumnExt; import org.jdesktop.test.AncientSwingTeam; import org.jdesktop.test.CellEditorReport; 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.*; /** * Tests of <code>JXTable</code>. * * * @author Jeanette Winzenburg */ @RunWith(JUnit4.class) public class JXTableUnitTest extends InteractiveTestCase { private static final Logger LOG = Logger.getLogger(JXTableUnitTest.class .getName()); protected DynamicTableModel tableModel = null; protected TableModel sortableTableModel; // flag used in setup to explicitly choose LF private boolean defaultToSystemLF; // stored ui properties to reset in teardown private Object uiTableRowHeight; private StringValue sv; private JXTable table; public JXTableUnitTest() { super("JXTable unit test"); } /** * Issue #1563-swingx: find cell that was clicked for componentPopup * * Test api and event firing. */ @Test public void testPopupTriggerLocationAvailable() { JXTable table = new JXTable(10, 3); 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() { JXTable table = new JXTable(10, 3); 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() { JXTable table = new JXTable(10, 3); 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 #1561-swingx: add api to get TableColumn/Ext at point */ @Test public void testGetColumnAtPoint() { JXTable table = new JXTable(10, 3); int x = table.getColumn(0).getWidth() + 10; TableColumn second = table.getColumn(new Point(x, 20)); assertSame(table.getColumn(1), second); } /** * Issue #1561-swingx: add api to get TableColumn/Ext at point */ @Test public void testGetColumnExtAtPoint() { JXTable table = new JXTable(10, 3); int x = table.getColumn(0).getWidth() + 10; TableColumn second = table.getColumnExt(new Point(x, 20)); assertSame(table.getColumnExt(1), second); } //------- start testing Issue #1535-swingx /** * Sanity: initially valid entry without forcing edit is behaving as expected */ @Test public void testGenericEditorXValidValue() { JTable table = new JXTable(create1535TableModel()); table.setValueAt(new ThrowingDummy("valid"), 0, throwOnEmpty); assertStoppedEventOnValidValue(table, 0, throwOnEmpty, false); } /** * Test editor firing when empty value is valid */ @Test public void testGenericEditorXValidValueAlways() { JTable table = new JXTable(create1535TableModel()); assertStoppedEventOnValidValue(table, 0, takeEmpty, false); assertTrue(table.getValueAt(0, takeEmpty) instanceof TakeItAllDummy); } /** * Editing a not-null value with empty text */ @Test public void testGenericEditorXEmptyValueInitiallyValid() { JTable table = new JXTable(create1535TableModel()); ThrowingDummy validValue = new ThrowingDummy("valid"); table.setValueAt(validValue, 0, throwOnEmpty); assertNoStoppedEventOnEmptyValue(table, 0, throwOnEmpty, true); assertEquals(validValue, table.getValueAt(0, throwOnEmpty)); } /** * Editing a null value with empty text. */ @Test public void testGenericEditorXEmptyValue() { JTable table = new JXTable(create1535TableModel()); assertNoStoppedEventOnEmptyValue(table, 0, throwOnEmpty, false); assertEquals(null, table.getValueAt(0, throwOnEmpty)); } /** * Asserts that stopping an edit with empty value (aka: invalid) * * - stopCellEditing returns false * - does not fire editing stopped * * Starts an edit on the given cell and stop the editor to analyse. * * * @param table * @param row * @param column * @param forceEmpty if true, set the editing textField's text property * to empty string before stopping */ protected static void assertNoStoppedEventOnEmptyValue(JTable table, int row, int column, boolean forceEmpty) { table.editCellAt(row, column); CellEditorReport report = new CellEditorReport(); DefaultCellEditor cellEditor = (DefaultCellEditor) table.getCellEditor(); if (forceEmpty) { ((JTextField) cellEditor.getComponent()).setText(""); } cellEditor.addCellEditorListener(report); assertFalse("empty value is invalid, refuse stop", cellEditor.stopCellEditing()); assertEquals("was invalid edit, must not fire stoppedEvent", 0, report.getStoppedEventCount()); } protected static void assertStoppedEventOnValidValue(JTable table, int row, int column, boolean forceEmpty) { table.editCellAt(row, column); CellEditorReport report = new CellEditorReport(); DefaultCellEditor cellEditor = (DefaultCellEditor) table.getCellEditor(); if (forceEmpty) { ((JTextField) cellEditor.getComponent()).setText(""); } cellEditor.addCellEditorListener(report); assertTrue("empty value is valid", cellEditor.stopCellEditing()); assertEquals("was valid edit, must fire single stoppedEvent", 1, report.getStoppedEventCount()); } protected static DefaultTableModel create1535TableModel() { DefaultTableModel model = new DefaultTableModel(10, 2) { @Override public Class<?> getColumnClass(int columnIndex) { return columnIndex == 0 ? SilentDummy.class : columnIndex == 1 ? ThrowingDummy.class : TakeItAllDummy.class; } }; model.setColumnIdentifiers(new Object[]{"silently refusing edits", "throwing on empty", "take all"}); return model; } protected static final int refuseEdit = 0; protected static final int throwOnEmpty = 1; protected static final int takeEmpty = 2; /** * No constructor with String param: not editable. */ public static class SilentDummy { public String value; } /** * Constructor with String param which takes any string. */ public static class TakeItAllDummy { public String value; public TakeItAllDummy(String value) { this.value = value; } } /** * Constructor with string param which must not be empty: * fires editingStopped even if empty (aka: not valid) */ public static class ThrowingDummy { public ThrowingDummy(String value) { if (value == null || "".equals(value)) { throw new IllegalArgumentException("don't feed me air!!"); } } } //------------------- end testing #1535-swingx @Test public void testHyperlinkDefaultRenderer() { // This test will not work in a headless configuration. if (GraphicsEnvironment.isHeadless()) { LOG.fine("cannot run ui test - headless environment: URI-renderer not registered"); return; } assertHyperlinkProvider(URI.class); // assertHyperlinkProvider(URL.class); } private void assertHyperlinkProvider(Class<?> clazz) { DefaultTableRenderer renderer = (DefaultTableRenderer) table.getDefaultRenderer(clazz); assertTrue("expected hyperlinkProvider but was:" + renderer.getComponentProvider(), renderer.getComponentProvider() instanceof HyperlinkProvider ); } /** * Issue #1422-swingx: setColumnSequence works incorrectly */ @Test public void testSetColumnSequence() { int numColumns = 5; JXTable table = new JXTable(10, numColumns); //hide first column TableColumnExt columnExt = table.getColumnExt(0); columnExt.setVisible(false); List<TableColumn> allColumns = table.getColumns(true); List<Object> identifiers = new ArrayList<Object>(); for (TableColumn tableColumn : allColumns) { identifiers.add(tableColumn.getIdentifier()); } Collections.reverse(identifiers); table.setColumnSequence(identifiers.toArray()); assertEquals(numColumns, table.getColumnCount(true)); assertEquals(false, columnExt.isVisible()); assertEquals(numColumns -1, table.getColumnCount()); } /** * Issue #1392-swingx: ColumnControl lost on change of CO and LAF */ @Test public void testColumnControlOnUpdateCO() { JXTable table = new JXTable(10, 2); JScrollPane scrollPane = new JScrollPane(table); table.setColumnControlVisible(true); toggleComponentOrientation(scrollPane); // scrollPane.setLayout(new ScrollPaneLayout()); assertSame("sanity: column control in trailing corner", table.getColumnControl(), scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); assertNull("column control must not be in leading corner", scrollPane.getCorner(JScrollPane.UPPER_LEADING_CORNER)); } /** * Issue #1392-swingx: ColumnControl lost on change of CO and LAF */ @Test public void testColumnControlOnUpdateUI() { JXTable table = new JXTable(10, 2); JScrollPane scrollPane = new JScrollPane(table); table.setColumnControlVisible(true); scrollPane.setLayout(new ScrollPaneLayout()); assertSame("sanity: column control survives setLayout", table.getColumnControl(), scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); toggleComponentOrientation(scrollPane); // scrollPane.setLayout(new ScrollPaneLayout()); assertSame("sanity: column control in trailing corner", table.getColumnControl(), scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); assertNull("column control must not be in leading corner", scrollPane.getCorner(JScrollPane.UPPER_LEADING_CORNER)); } @Test public void testPrepareRenderer() { table.setModel(sortableTableModel); TableCellRenderer renderer = table.getCellRenderer(0, AncientSwingTeam.INTEGER_COLUMN); Component comp = table.prepareRenderer(renderer, 0, AncientSwingTeam.INTEGER_COLUMN); assertSame(comp, table.prepareRenderer(0, AncientSwingTeam.INTEGER_COLUMN)); } @Test public void testSortedColumnIndex() { table.setModel(sortableTableModel); assertEquals(-1, table.getSortedColumnIndex()); table.toggleSortOrder(0); assertEquals(0, table.getSortedColumnIndex()); TableColumnExt columnExt = table.getColumnExt(0); columnExt.setVisible(false); assertEquals(-1, table.getSortedColumnIndex()); columnExt.setVisible(true); } /** * Issue #1173-swingx: clarify table's responsibities in sorter configuration. */ @Test public void testHasSortController() { assertTrue(table.hasSortController()); table.setRowSorter(new TableRowSorter<TableModel>()); assertFalse(table.hasSortController()); } /** * Issue #1173-swingx: clarify table's responsibities in sorter configuration. */ @Test public void testControlsSorterPropertiesOnSettingSorterTrue() { assertControlsSorterPropertiesTrue(true); } /** * Issue #1173-swingx: clarify table's responsibities in sorter configuration. */ @Test public void testControlsSorterPropertiesOnSettingPropertyTrue() { assertControlsSorterPropertiesTrue(false); } /** * Issue #1173-swingx: clarify table's responsibities in sorter configuration. */ @Test public void testControlsSorterPropertiesOnSettingSorterFalse() { assertControlsSorterPropertiesFalse(true); } /** * Issue #1173-swingx: clarify table's responsibities in sorter configuration. */ @Test public void testControlsSorterPropertiesOnSettingPropertyFalse() { assertControlsSorterPropertiesFalse(false); } /** * RowSorter properties not touched if getControlsSorterProperties false. */ private void assertControlsSorterPropertiesFalse(boolean setSorter) { table.setAutoCreateRowSorter(false); SortOrder[] cycle = new SortOrder[] {SortOrder.DESCENDING, SortOrder.UNSORTED}; table.setSortOrderCycle(cycle); table.setSortsOnUpdates(!table.getSortsOnUpdates()); table.setSortable(!table.isSortable()); if (setSorter) { table.setRowSorter(new TableSortController<TableModel>(table.getModel())); assertFalse("StringValueProvider propagated to controller", table.getStringValueRegistry().equals(getSortController(table).getStringValueProvider())); } assertEquals("sortsOnUpdates propagated to controller", !table.getSortsOnUpdates(), getSortController(table).getSortsOnUpdates()); assertEquals("sortable propagated to controller", !table.isSortable(), getSortController(table).isSortable()); assertFalse("sortOrderCycle propagated to controller", Arrays.equals(table.getSortOrderCycle(), getSortController(table).getSortOrderCycle())); } /** * RowSorter properties updated on getControlsSorterProperties true. */ private void assertControlsSorterPropertiesTrue(boolean setSorter) { SortOrder[] cycle = new SortOrder[] {SortOrder.DESCENDING, SortOrder.UNSORTED}; table.setSortOrderCycle(cycle); table.setSortsOnUpdates(!table.getSortsOnUpdates()); table.setSortable(!table.isSortable()); if (setSorter) { table.setRowSorter(new TableSortController<TableModel>(table.getModel())); } assertEquals("sortsOnUpdates propagated to controller", table.getSortsOnUpdates(), getSortController(table).getSortsOnUpdates()); assertEquals("sortable propagated to controller", table.isSortable(), getSortController(table).isSortable()); assertTrue("sortOrderCycle propagated to controller", Arrays.equals(table.getSortOrderCycle(), getSortController(table).getSortOrderCycle())); assertEquals("StringValueProvider propagated to controller", table.getStringValueRegistry(), getSortController(table).getStringValueProvider()); } //-------------- sort-related properties on table /** * Issue #1122-swingx: re-introduce JXTable sort api. * Test sortsOnUpdate property. * Here: test default value and change notification. */ @Test public void testSortsOnUpdateChangeNotification() { assertEquals("initial sortsOnUpdate", true, table.getSortsOnUpdates()); PropertyChangeReport report = new PropertyChangeReport(table); table.setSortsOnUpdates(false); TestUtils.assertPropertyChangeEvent(report, "sortsOnUpdates", true, false); } /** * Setting table's sortable property updates controller. */ @Test public void testSortOrderCycleNotification() { SortOrder[] cycle = new SortOrder[] {SortOrder.DESCENDING, SortOrder.UNSORTED}; PropertyChangeReport report = new PropertyChangeReport(table); table.setSortOrderCycle(cycle); TestUtils.assertPropertyChangeEvent(report, "sortOrderCycle", DefaultSortController.getDefaultSortOrderCycle(), table.getSortOrderCycle()); } // --------- add usage of StringValueRegistry into JXTable /** * Issue #1145-swingx: re-enable filtering with single-string-representation. * was: Issue #767-swingx: consistent string representation. * * Here: test PatternFilter uses getStringXX */ @Test public void testTableGetStringUsedInPatternFilter() { JXTableT table = new JXTableT(new AncientSwingTeam()); table.setDefaultRenderer(Color.class, new DefaultTableRenderer(sv)); RowFilter<Object, Integer> filter = RowFilter.regexFilter("R/G/B: -2.*", 2); table.setRowFilter(filter); assertTrue(table.getRowCount() > 0); assertEquals(sv.getString(table.getValueAt(0, 2)), table.getStringAt(0, 2)); } /** * Issue 1145-swingx: re-enable filter to use string representation. * Here: test that cell-location StringValue look up setColumnModel. * */ @Test public void testStringValueRegistryFromSetColumnModel() { JXTable table = new JXTable(); table.setAutoCreateColumnsFromModel(false); table.setModel(createModelDefaultColumnClasses(4)); final int column = 2; // custom column factory which sets per-column renderer ColumnFactory factory = new ColumnFactory() { @Override public void configureTableColumn(TableModel model, TableColumnExt columnExt) { super.configureTableColumn(model, columnExt); if (columnExt.getModelIndex() == column) columnExt.setCellRenderer(new DefaultTableRenderer()); } }; DefaultTableColumnModelExt model = new DefaultTableColumnModelExt(); for (int i = 0; i < table.getModel().getColumnCount(); i++) { model.addColumn(factory.createAndConfigureTableColumn(table.getModel(), i)); } table.setColumnModel(model); StringValueRegistry provider = table.getStringValueRegistry(); assertEquals(table.getCellRenderer(0, column), provider.getStringValue(0, column)); } /** * Issue 1145-swingx: re-enable filter to use string representation. * Here: test that cell-location StringValue look up initial per-column renderer * */ @Test public void testStringValueRegistryFromColumnFactory() { JXTable table = new JXTable(); final int column = 2; // custom column factory which sets per-column renderer ColumnFactory factory = new ColumnFactory() { @Override public void configureTableColumn(TableModel model, TableColumnExt columnExt) { super.configureTableColumn(model, columnExt); if (columnExt.getModelIndex() == column) columnExt.setCellRenderer(new DefaultTableRenderer()); } }; table.setColumnFactory(factory); table.setModel(createModelDefaultColumnClasses(4)); StringValueRegistry provider = table.getStringValueRegistry(); assertEquals(table.getCellRenderer(0, column), provider.getStringValue(0, column)); } /** * Issue 1145-swingx: re-enable filter to use string representation. * Here: test that cell-location StringValue look up updates per-column renderer * on setting on TableColumn. */ @Test public void testStringValueRegistryFromColumnRendererChange() { JXTable table = new JXTable(createModelDefaultColumnClasses(4)); int column = 2; table.getColumn(column).setCellRenderer(new DefaultTableRenderer()); StringValueRegistry provider = table.getStringValueRegistry(); assertEquals(table.getCellRenderer(0, column), provider.getStringValue(0, column)); } /** * Issue 1145-swingx: re-enable filter to use string representation. * Here: test that cell-location StringValue look up is updated with model in setModel. */ @Test public void testStringValueRegistryWithModelSet() { table.setModel(createModelDefaultColumnClasses(4)); StringValueRegistry provider = table.getStringValueRegistry(); for (int i = 0; i < table.getColumnCount(); i++) { assertEquals("stringValue must be same as renderer for class: " + table.getColumnClass(i), table.getDefaultRenderer(table.getColumnClass(i)), provider.getStringValue(0, i)); } } /** * Issue 1145-swingx: re-enable filter to use string representation. * Here: test that cell-location StringValue look up is updated with model initially. */ @Test public void testStringValueRegistryWithModelInitial() { JXTable table = new JXTable(createModelDefaultColumnClasses(4)); StringValueRegistry provider = table.getStringValueRegistry(); for (int i = 0; i < table.getColumnCount(); i++) { assertEquals("stringValue must be same as renderer for class: " + table.getColumnClass(i), table.getDefaultRenderer(table.getColumnClass(i)), provider.getStringValue(0, i)); } } /** * Issue 1145-swingx: re-enable filter to use string representation. * Here: test that default per-class string values are removed if not * of type StringValue. */ @Test public void testStringValueRegistryUpdatedRemoved() { table.setDefaultRenderer(Number.class, new DefaultTableCellRenderer()); assertEquals(null, table.getStringValueRegistry().getStringValue(Number.class)); } /** * Issue 1145-swingx: re-enable filter to use string representation. * Here: test that default per-class string values are updated. */ @Test public void testStringValueRegistryUpdated() { table.setDefaultRenderer(Component.class, new DefaultTableRenderer()); assertEquals(table.getDefaultRenderer(Component.class), table.getStringValueRegistry().getStringValue(Component.class)); } /** * Issue 1145-swingx: re-enable filter to use string representation. * Here: test that default per-class string values are registered initially. */ @Test public void testStringValueRegistryInitial() { StringValueRegistry provider = table.getStringValueRegistry(); for (int i = 0; i < DEFAULT_COLUMN_TYPES.length; i++) { assertEquals("stringValue must be same as renderer for class: " + DEFAULT_COLUMN_NAMES[i], table.getDefaultRenderer(DEFAULT_COLUMN_TYPES[i]), provider.getStringValue(DEFAULT_COLUMN_TYPES[i])); } } /** * Creates and returns a TableModel with default column classes, as * defined by DEFAULT_COLUMN_TYPES. * @return */ private TableModel createModelDefaultColumnClasses(int rows) { DefaultTableModel model = new DefaultTableModel(DEFAULT_COLUMN_NAMES, rows) { @Override public Class<?> getColumnClass(int columnIndex) { return DEFAULT_COLUMN_TYPES[columnIndex]; } }; return model; } private static String[] DEFAULT_COLUMN_NAMES = { "Object", "Number", "Date", "Icon", "ImageIcon", "Boolean"}; private static Class<?>[] DEFAULT_COLUMN_TYPES = new Class[] { Object.class, Number.class, Date.class, Icon.class, ImageIcon.class, Boolean.class }; //--------------------- sort state on modifications /** * For comparison ... * * Issue #1132-swingx: JXTable - define and implement behaviour on remove column */ @Test public void testSortStateAfterRemoveColumn() { JTable table = new JTable(); table.setAutoCreateRowSorter(true); table.setModel(new AncientSwingTeam()); table.getRowSorter().toggleSortOrder(0); List<? extends SortKey> sortKeys = table.getRowSorter().getSortKeys(); table.removeColumn(table.getColumnModel().getColumn(0)); assertEquals(sortKeys, table.getRowSorter().getSortKeys()); } /** * * Issue #1132-swingx: JXTable - define and implement behaviour on remove column * * was: Issue #53-swingx: interactive sorter not removed if column removed. * */ @Test public void testSorterAfterColumnRemoved() { JXTable table = new JXTable(sortableTableModel); TableColumnExt columnX = table.getColumnExt(0); table.toggleSortOrder(0); table.removeColumn(columnX); // this is trivially true, as only contained columns are found .... assertNull("sorter must be removed when column removed", table.getSortedColumn()); // check the sort keys instead assertEquals("consistency with core: sortKeys untouched after remove column", 1, table.getRowSorter().getSortKeys().size()); } /** * Interactive sorter must be active if column is hidden. */ @Test public void testSorterAfterColumnHidden() { JXTable table = new JXTable(sortableTableModel); TableColumnExt columnX = table.getColumnExt(0); table.toggleSortOrder(0); columnX.setVisible(false); assertEquals("interactive sorter must be same as sorter in column", columnX, table.getSortedColumn()); } // --------------- sortable/comparator property /** * JXTable has responsibility to guarantee usage of * TableColumnExt comparator. * */ @Test public void testComparatorToController() { JXTable table = new JXTable(new AncientSwingTeam()); TableColumnExt columnX = table.getColumnExt(0); columnX.setComparator(Collator.getInstance()); assertSame(columnX.getComparator(), getSortController(table).getComparator(columnX.getModelIndex())); } /** * JXTable has responsibility to guarantee usage of * TableColumnExt comparator. * */ @Test public void testComparatorToControllerInSetSorter() { JXTable table = new JXTable(new AncientSwingTeam()); TableColumnExt columnX = table.getColumnExt(0); columnX.setComparator(Collator.getInstance()); table.setRowSorter(new TableSortController<TableModel>(table.getModel())); assertSame(columnX.getComparator(), getSortController(table).getComparator(columnX.getModelIndex())); } /** * Issue 1131-swingx: JXTable must guarantee to pass column sortable property * always. * * Here: test that all columns in sortController are updated after structureChanged event. * with a real structureChanged (more columns) */ @Test public void testSortableColumnPropertyOnStructureChangedRemoveColumn() { SortableTestFactory factory = new SortableTestFactory(); table.setColumnFactory(factory); // quick access to fire a structure change DefaultTableModel model = new DefaultTableModel(10, 5); table.setModel(model); // trigger structureChanged model.setColumnCount(model.getColumnCount() - 2); factory.assertSortableColumnState(table); } /** * Issue 1131-swingx: JXTable must guarantee to pass column sortable property * always. * * Here: test that all columns in sortController are updated after structureChanged event. * with a real structureChanged (more columns) */ @Test public void testSortableColumnPropertyOnStructureChangedAddColumn() { SortableTestFactory factory = new SortableTestFactory(); table.setColumnFactory(factory); // quick access to fire a structure change DefaultTableModel model = new DefaultTableModel(10, 5); table.setModel(model); // trigger structureChanged model.setColumnCount(model.getColumnCount() + 2); factory.assertSortableColumnState(table); } /** * Issue 1131-swingx: JXTable must guarantee to pass column sortable property * always. * * Here: test that all columns in sortController are updated after setColumnModel. */ @Test public void testSortableSetColumnModel() { SortableTestFactory factory = new SortableTestFactory(); table.setAutoCreateColumnsFromModel(false); table.setModel(sortableTableModel); DefaultTableColumnModelExt columnModel = new DefaultTableColumnModelExt(); // add two columns, one sortable, one not sortable columnModel.addColumn(factory.createAndConfigureTableColumn(sortableTableModel, 0)); columnModel.addColumn(factory.createAndConfigureTableColumn(sortableTableModel, 1)); // hide unsortable column columnModel.getColumnExt(1).setVisible(false); table.setColumnModel(columnModel); factory.assertSortableColumnState(table); } /** * Issue 1131-swingx: JXTable must guarantee to pass column sortable property * always. * * Here: test that all columns in sortController are updated after addColumn. */ @Test public void testSortableAddColumn() { SortableTestFactory factory = new SortableTestFactory(); table.setAutoCreateColumnsFromModel(false); table.setModel(sortableTableModel); // add two columns, one sortable, one not sortable table.addColumn(factory.createAndConfigureTableColumn(sortableTableModel, 0)); table.addColumn(factory.createAndConfigureTableColumn(sortableTableModel, 1)); factory.assertSortableColumnState(table); } /** * Issue 1131-swingx: JXTable must guarantee to pass column sortable property * always. * * Here: test that all columns in sortController are updated after setModel. */ @Test public void testSortableColumnPropertyOnSetModel() { SortableTestFactory factory = new SortableTestFactory(); table.setColumnFactory(factory); table.setModel(sortableTableModel); factory.assertSortableColumnState(table); } /** * Convenience factory for testing. Configures every odd column as not sortable * and has a method to assert that the sortable state is as configured. */ public static class SortableTestFactory extends ColumnFactory { @Override public void configureTableColumn(TableModel model, TableColumnExt columnExt) { super.configureTableColumn(model, columnExt); if (columnExt.getModelIndex() % 2 != 0) { // make odd columns not-sortable columnExt.setSortable(false); } else { // set per-column comparator for even columns columnExt.setComparator(Collator.getInstance()); } } public void assertSortableColumnState(JXTable table) { List<TableColumn> columns = table.getColumns(true); for (TableColumn tableColumn : columns) { int i = tableColumn.getModelIndex(); assertEquals("odd/even columns must be not/-sortable: " + i, i % 2 == 0, getSortController(table).isSortable(i)); if (tableColumn instanceof TableColumnExt) { Comparator<?> comparator = ((TableColumnExt) tableColumn).getComparator(); // JW: need to check against null because sorter might have its own // ideas about default comparators if (comparator != null) { assertSame("comparator must be same: " + i, comparator, getSortController(table).getComparator(i)); } } } } } /** * Issue 1131-swingx: JXTable must guarantee to pass column sortable property * always. * * Here: test that table's sortable property is propagated on setModel. */ @Test public void testSortableTablePropertyOnSetModel() { table.setSortable(false); table.setModel(sortableTableModel); assertEquals(false, getSortController(table).isSortable()); } /** * Issue 1131-swingx: table must unsort column on sortable change. * * Invalid as of switch to core sorting. On the contrary, the table must * not go clever under the feat of the controller. * */ @Test public void testTableUnsortedColumnOnColumnSortableChange() { JXTable table = new JXTable(10, 2); TableColumnExt columnExt = table.getColumnExt(0); table.toggleSortOrder(0); assertTrue("sanity: sorted", SortUtils.isSorted(table.getSortOrder(0))); columnExt.setSortable(false); assertTrue("changing sortability must not change sort state (with default controller)", SortUtils.isSorted(table.getSortOrder(0))); } /** * JXTable has responsibility to respect TableColumnExt * sortable property. * */ @Test public void testSetSortOrderByIdentifierColumnNotSortable() { JXTable table = new JXTable(sortableTableModel); Object identifier = "Last Name"; TableColumnExt columnX = table.getColumnExt(identifier); // make column not sortable. columnX.setSortable(false); table.setSortOrder(identifier, SortOrder.ASCENDING); assertEquals("unsortable column must be unsorted", SortOrder.UNSORTED, table.getSortOrder(identifier)); } /** * JXTable has responsibility to respect TableColumnExt * sortable property. * */ @Test public void testToggleSortOrderByIdentifierColumnNotSortable() { JXTable table = new JXTable(sortableTableModel); Object identifier = "Last Name"; TableColumnExt columnX = table.getColumnExt(identifier); // old way: make column not sortable. columnX.setSortable(false); table.toggleSortOrder(identifier); assertEquals("unsortable column must be unsorted", SortOrder.UNSORTED, table.getSortOrder(identifier)); } /** * JXTable has responsibility to respect TableColumnExt * sortable property. * */ @Test public void testSetSortOrderColumnNotSortable() { JXTable table = new JXTable(sortableTableModel); TableColumnExt columnX = table.getColumnExt(0); // old way: make column not sortable. columnX.setSortable(false); table.setSortOrder(0, SortOrder.ASCENDING); assertEquals("unsortable column must be unsorted", SortOrder.UNSORTED, table.getSortOrder(0)); } /** * JXTable has responsibility to respect TableColumnExt * sortable property. * */ @Test public void testToggleSortOrderColumnNotSortable() { JXTable table = new JXTable(sortableTableModel); TableColumnExt columnX = table.getColumnExt(0); // old way: make column not sortable. columnX.setSortable(false); table.toggleSortOrder(0); assertEquals("unsortable column must be unsorted", SortOrder.UNSORTED, table.getSortOrder(0)); } /** * Setting table's sortable property updates controller. */ @Test public void testTableRowFilterSynchedToController() { RowFilter<Object, Object> filter = RowFilters.regexFilter(".*"); table.setRowFilter(filter); assertEquals(filter, getSortController(table).getRowFilter()); assertEquals(filter, table.getRowFilter()); } //----------------- table sort api /* NOTE: if applicable we test the api on columns which are not of type TableColumnExt * Doing so because the pre-Mustang implementation worked on columns of extended type only. * */ /** * add api to access the sorted column. * */ @Test public void testSortedColumn() { JXTable table = createTableWithCoreColumns(); table.toggleSortOrder(1); assertEquals(table.getColumn(1), table.getSortedColumn()); table.toggleSortOrder(2); assertEquals(table.getColumn(2), table.getSortedColumn()); } /** * Create and return a table with core column type. * * @return */ private JXTable createTableWithCoreColumns() { JXTable table = new JXTable(); table.setAutoCreateColumnsFromModel(false); table.setModel(sortableTableModel); TableColumnModel columnModel = new DefaultTableColumnModelExt(); for (int i = 0; i < sortableTableModel.getColumnCount(); i++) { TableColumn column = new TableColumn(i); column.setHeaderValue(sortableTableModel.getColumnName(i)); column.setIdentifier(sortableTableModel.getColumnName(i)); columnModel.addColumn(column); } table.setColumnModel(columnModel); return table; } /** * Unsetting sortOrder of one column must not remove sorts in other columns. * * Previously we had single column sort only, so remove that single was same * as reset all. */ @Test public void testSetUnsortedSortOrder() { JXTable table = createTableWithCoreColumns(); // prepare: two columns sorted table.setSortOrder(0, SortOrder.ASCENDING); table.setSortOrder(1, SortOrder.ASCENDING); assertEquals("sanity: really multiple columns sorted", 2, table.getRowSorter().getSortKeys().size()); // unsort primary sort column table.setSortOrder(1, SortOrder.UNSORTED); assertEquals("secondary sort column must still be sorted", SortOrder.ASCENDING, table.getSortOrder(0)); } /** * Unsetting sortOrder of one column must not remove sorts in other columns. * * Previously we had single column sort only, so remove that single was same * as reset all. */ @Test public void testSetUnsortedSortOrderByIdentifier() { JXTable table = createTableWithCoreColumns(); // prepare: two columns sorted table.setSortOrder("First Name", SortOrder.ASCENDING); table.setSortOrder("Last Name", SortOrder.ASCENDING); assertEquals("sanity: really multiple columns sorted", 2, table.getRowSorter().getSortKeys().size()); // unsort primary sort column table.setSortOrder("Last Name", SortOrder.UNSORTED); assertEquals("secondary sort column must still be sorted", SortOrder.ASCENDING, table.getSortOrder("First Name")); } /** * Programmatic sorting of hidden column (through table api). * */ @Test public void testSetSortOrderHiddenColumn() { // hidden only defined for TableColumnExt JXTable table = new JXTable(sortableTableModel); Object identifier = "Last Name"; TableColumnExt columnExt = table.getColumnExt(identifier); columnExt.setVisible(false); table.setSortOrder(identifier, SortOrder.ASCENDING); assertEquals("sorted column must be at " + identifier, columnExt, table.getSortedColumn()); assertEquals("column must be sorted after setting sortOrder on " + identifier, SortOrder.ASCENDING, table.getSortOrder(identifier)); } /** * added xtable.setSortOrder(Object, SortOrder) * */ @Test public void testSetSortOrderByIdentifier() { JXTable table = createTableWithCoreColumns(); Object identifier = "Last Name"; TableColumn columnExt = table.getColumn(identifier); table.setSortOrder(identifier, SortOrder.ASCENDING); assertEquals("sorted column must be at " + identifier, columnExt, table.getSortedColumn()); assertEquals("column must be sorted after setting sortOrder on " + identifier, SortOrder.ASCENDING, table.getSortOrder(identifier)); } /** * testing new sorter api: * getSortOrder(int), toggleSortOrder(int). * */ @Test public void testToggleSortOrder() { JXTable table = createTableWithCoreColumns(); assertSame(SortOrder.UNSORTED, table.getSortOrder(0)); table.toggleSortOrder(0); assertSame(SortOrder.ASCENDING, table.getSortOrder(0)); // sanity: other columns uneffected assertSame(SortOrder.UNSORTED, table.getSortOrder(1)); table.toggleSortOrder(0); assertSame(SortOrder.DESCENDING, table.getSortOrder(0)); table.resetSortOrder(); assertSame(SortOrder.UNSORTED, table.getSortOrder(0)); } /** * testing new sorter api: * toggleSortOrder(Object), resetSortOrder. * */ @Test public void testToggleSortOrderByIdentifier() { JXTable table = createTableWithCoreColumns(); Object firstColumn = "First Name"; Object secondColumn = "Last Name"; assertSame(SortOrder.UNSORTED, table.getSortOrder(secondColumn)); table.toggleSortOrder(firstColumn); assertSame(SortOrder.ASCENDING, table.getSortOrder(firstColumn)); // sanity: other columns uneffected assertSame(SortOrder.UNSORTED, table.getSortOrder(secondColumn)); table.toggleSortOrder(firstColumn); assertSame(SortOrder.DESCENDING, table.getSortOrder(firstColumn)); table.resetSortOrder(); assertSame(SortOrder.UNSORTED, table.getSortOrder(firstColumn)); } /** * added xtable.setSortOrder(int, SortOrder) * */ @Test public void testSetSortOrder() { JXTable table = createTableWithCoreColumns(); int col = 0; TableColumn columnExt = table.getColumn(col); table.setSortOrder(col, SortOrder.ASCENDING); assertEquals("sorted column must be at " + col, columnExt, table.getSortedColumn()); assertEquals("column must be sorted after setting sortOrder on " + col, SortOrder.ASCENDING, table.getSortOrder(col)); } /** * resetSortOrders didn't check for tableHeader != null. * Didn't show up before new sorter api because method was protected and * only called from JXTableHeader. * */ @Test public void testResetSortOrderNPE() { JXTable table = new JXTable(sortableTableModel); table.setTableHeader(null); table.resetSortOrder(); } // --------------------- SortController/RowSorter /** * Test that JXTable uses TableSortController by default. */ @Test public void testSortController() { assertTrue("default sorter expected TableRowSorter, but was:" + table.getRowSorter(), table.getRowSorter() instanceof TableSortController<?>); } /** * core issue: rowSorter replaced on setAutoCreateRowSorter even without change to flag. */ @Test public void testSetAutoCreateRowSorter() { JXTable table = new JXTable(new AncientSwingTeam()); RowSorter<?> sorter = table.getRowSorter(); assertNotNull("sanity: core rowSorter is created", sorter); table.setAutoCreateRowSorter(true); assertSame(sorter, table.getRowSorter()); } /** * Sanity test: we are tricksing super to think that auto-create is off, but * only super. At all other times the property must be as set. * */ @Test public void testAutoCreateRowSorterGetterSetterSynched() { table.setAutoCreateRowSorter(false); assertEquals(false, table.getAutoCreateRowSorter()); table.setAutoCreateRowSorter(true); assertEquals(true, table.getAutoCreateRowSorter()); } /** * Sanity test: we are tricksing super to think that auto-create is off, but * only super. Here we test if setModel does reset the property to its old state. * */ @Test public void testSetModelKeepsCreateRowSorterFlagTrue() { assertSetModelKeepsCreateSorterFlag(table, true); } /** * Sanity test: we are tricksing super to think that auto-create is off, but * only super. Here we test if setModel does reset the property to its old state. * */ @Test public void testSetModelKeepsCreateRowSorterFlagFalse() { assertSetModelKeepsCreateSorterFlag(table, false); } /** * @param table * @param flag */ private void assertSetModelKeepsCreateSorterFlag(JTable table, boolean flag) { table.setAutoCreateRowSorter(flag); table.setModel(new DefaultTableModel()); assertEquals(flag, table.getAutoCreateRowSorter()); } /** * Test that setModel uses factory method to create rowSorter. */ @Test public void testSetModelCreateDefaultRowSorter() { JXTable table = new JXRTable(); table.setModel(new AncientSwingTeam()); assertTrue("table must install default rowSorter, but was: " + table.getRowSorter().getClass(), table.getRowSorter() instanceof XTableRowSorter<?>); assertSame("default RowSorter must be configured with table model", table.getModel(), table.getRowSorter().getModel() ); } /** * Test that JXTable uses factroy method when auto-creating a rowSorter. */ @Test public void testCreateDefaultRowSorterOverridden() { JXTable table = new JXRTable(); assertTrue("table must install default rowSorter, but was: " + table.getRowSorter().getClass(), table.getRowSorter() instanceof XTableRowSorter<?>); assertSame("default RowSorter must be configured with table model", table.getModel(), table.getRowSorter().getModel() ); } public static class JXRTable extends JXTable { @Override protected RowSorter<? extends TableModel> createDefaultRowSorter() { XTableRowSorter<TableModel> sorter = new XTableRowSorter<TableModel>(); sorter.setModel(getModel()); return sorter; } } /** * quick class to check that JXTable uses its factory method to auto-create */ public static class XTableRowSorter<M extends TableModel> extends TableRowSorter<M> { } /** * Sanity: default rowSorter has same model as table. */ @Test public void testAutoCreateRowSorterModelInstalled() { assertSame("model must be same", table.getModel(), table.getRowSorter().getModel()); } /** * JXTable auto-create rowsorter is true by default. */ @Test public void testAutoCreateRowSorter() { assertEquals("table must have default auto-create rowsorter true", true, table.getAutoCreateRowSorter()); } //------------------- ComponentAdapter /** * Issue #??- ComponentAdapter's default implementation does not * return the value at the adapter's view state. * * Clarified the documentation: the assumption for the base implementation * is that model coordinates == view coordinates, that is it's up to * subclasses to implement the model correctly if they support different * coordinate systems. * */ @Test public void testComponentAdapterCoordinatesDocumentation() { final JXTable table = new JXTable(createAscendingModel(0, 10)); Object originalFirstRowValue = table.getValueAt(0,0); Object originalLastRowValue = table.getValueAt(table.getRowCount() - 1, 0); assertEquals("view row coordinate equals model row coordinate", table.getModel().getValueAt(0, 0), originalFirstRowValue); // sort first column - actually does not change anything order table.toggleSortOrder(0); // sanity assert assertEquals("view order must be unchanged ", table.getValueAt(0, 0), originalFirstRowValue); // invert sort table.toggleSortOrder(0); // sanity assert assertEquals("view order must be reversed changed ", table.getValueAt(0, 0), originalLastRowValue); ComponentAdapter adapter = new ComponentAdapter(table) { @Override public Object getColumnIdentifierAt(int columnIndex) { // TODO Auto-generated method stub return null; } @Override public int getColumnIndex(Object identifier) { // TODO Auto-generated method stub return 0; } @Override public String getColumnName(int columnIndex) { return null; } /** * {@inheritDoc} */ @Override public Object getFilteredValueAt(int row, int column) { return getValueAt(table.convertRowIndexToModel(row), column); } @Override public Object getValueAt(int row, int column) { return table.getModel().getValueAt(row, column); } @Override public Object getValue() { return getValueAt(table.convertRowIndexToModel(row), convertColumnIndexToModel(column)); } @Override public boolean hasFocus() { return false; } @Override public boolean isCellEditable(int row, int column) { return false; } @Override public boolean isEditable() { return false; } @Override public boolean isSelected() { return false; } }; assertEquals("adapter filteredValue expects row view coordinates", table.getValueAt(0, 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", table.getValueAt(0, 0), adapter.getValue()); assertEquals(adapter.getFilteredValueAt(0, adapter.getColumnCount() -1), adapter.getValue(adapter.getColumnCount()-1)); } /** * Test assumptions of accessing table model/view values through * the table's componentAdapter. * * PENDING JW: revisit - diff from above method? */ @Test public void testComponentAdapterCoordinates() { JXTable table = new JXTable(createAscendingModel(0, 10)); Object originalFirstRowValue = table.getValueAt(0,0); Object originalLastRowValue = table.getValueAt(table.getRowCount() - 1, 0); assertEquals("view row coordinate equals model row coordinate", table.getModel().getValueAt(0, 0), originalFirstRowValue); // sort first column - actually does not change anything order table.toggleSortOrder(0); // sanity asssert assertEquals("view order must be unchanged ", table.getValueAt(0, 0), originalFirstRowValue); // invert sort table.toggleSortOrder(0); // sanity assert assertEquals("view order must be reversed changed ", table.getValueAt(0, 0), originalLastRowValue); ComponentAdapter adapter = table.getComponentAdapter(); assertEquals("adapter filteredValue expects row view coordinates", table.getValueAt(0, 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", table.getValueAt(0, 0), adapter.getValue()); assertEquals("adapter.getValue must return value at adapter coordinates", table.getValueAt(0, 0), adapter.getValue(0)); } //------------------ end of sort-related tests /** * Issue #847-swingx: JXTable respect custom corner if columnControl not visible * * LAF provided corners are handled in core since jdk6u10. */ @Test public void testCornerRespectLAF() { Object corner = UIManager.get("Table.scrollPaneCornerComponent"); if (!(corner instanceof Class<?>)) { LOG.fine("cannont run - LAF doesn't provide corner component"); return; } final JXTable table = new JXTable(10, 2); final JScrollPane scrollPane = new JScrollPane(table); table.addNotify(); assertNotNull(scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); assertEquals(corner, scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER).getClass()); } /** * Issue #550-swingx: xtable must not reset columns' pref/size on * structureChanged if autocreate is false. * * here: width (was no problem, default columnFactory only sets pref) */ @Test public void testInitializeColumnWidth() { JXTable table = new JXTable(10, 2); table.setAutoResizeMode(JXTable.AUTO_RESIZE_OFF); table.setAutoCreateColumnsFromModel(false); int width = table.getColumn(0).getWidth() + 2; table.getColumn(0).setWidth(width); assertEquals("sanity: ", width, table.getColumn(0).getWidth()); table.tableChanged(null); assertEquals("structure changed must not resize column", width, table.getColumn(0).getWidth() ); } /** * Issue #550-swingx: xtable must not reset columns' pref/width on * structureChanged if autocreate is false. * * here: prefWidth */ @Test public void testInitializeColumnPrefWidth() { JXTable table = new JXTable(10, 2); table.setAutoResizeMode(JXTable.AUTO_RESIZE_OFF); table.setAutoCreateColumnsFromModel(false); int width = table.getColumn(0).getPreferredWidth() + 2; table.getColumn(0).setPreferredWidth(width); assertEquals("sanity: ", width, table.getColumn(0).getPreferredWidth()); table.tableChanged(null); assertEquals("structure changed must not resize column", width, table.getColumn(0).getPreferredWidth() ); } /** * Issue #847-swingx: JXTable respect custom corner if columnControl not visible * * Test correct un-/config on toggling the controlVisible property */ @Test public void testColumnControlVisible() { JXTable table = new JXTable(10, 2); JScrollPane scrollPane = new JScrollPane(table); table.setColumnControlVisible(true); assertSame("sanity: column control set", table.getColumnControl(), scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); table.setColumnControlVisible(false); assertEquals("columnControl must be removed from corner if not visible", null, scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); } /** * Issue #847-swingx: JXTable respect custom corner if columnControl not visible * * @throws Exception */ @Test public void testCornerRespectCustom() throws Exception { // This test will not work in a headless configuration. if (GraphicsEnvironment.isHeadless()) { LOG.fine("cannot run testCornerNPE - headless environment"); return; } final JXTable table = new JXTable(10, 2); final JScrollPane scrollPane = new JScrollPane(table); final JFrame frame = new JFrame(); frame.add(scrollPane); frame.pack(); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { JPanel panel = new JPanel(); scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER, panel); assertEquals("sanity ...", panel, scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); frame.remove(scrollPane); frame.add(scrollPane); if (table.isColumnControlVisible()) { assertEquals(table.getColumnControl(), scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); } else { assertEquals("xTable respects custom corner if columnControl invisible", panel, scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); } } }); } /** * Issue #844-swingx: JXTable throws NPE with custom corner. * * @throws Exception */ @Test public void testCornerNPE() throws Exception { // This test will not work in a headless configuration. if (GraphicsEnvironment.isHeadless()) { LOG.fine("cannot run testCornerNPE - headless environment"); return; } JXTable table = new JXTable(10, 2); final JScrollPane scrollPane = new JScrollPane(table); final JFrame frame = new JFrame(); frame.add(scrollPane); frame.pack(); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { scrollPane.setCorner(JScrollPane.UPPER_TRAILING_CORNER, new JPanel()); assertNotNull("sanity ...", scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); frame.remove(scrollPane); frame.add(scrollPane); } }); } /** * Issue #844-swingx: JXTable throws NPE with custom corner. * Regression testing (Issue #155-swingx) - scrollpane policy must be respected. * @throws Exception */ @Test public void testCornerNPEVerticalSPPolicy() throws Exception { // This test will not work in a headless configuration. if (GraphicsEnvironment.isHeadless()) { LOG.fine("cannot run testCornerNPE - headless environment"); return; } final JXTable table = new JXTable(10, 2); final JScrollPane scrollPane = new JScrollPane(table); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); table.setColumnControlVisible(true); final JFrame frame = new JFrame(); frame.add(scrollPane); frame.pack(); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { frame.remove(scrollPane); frame.add(scrollPane); assertSame(table.getColumnControl(), scrollPane.getCorner(JScrollPane.UPPER_TRAILING_CORNER)); table.setColumnControlVisible(false); assertEquals(JScrollPane.VERTICAL_SCROLLBAR_NEVER, scrollPane.getVerticalScrollBarPolicy()); } }); } /** * Issue #844-swingx: JXTable throws NPE with custom corner. * Regression testing (Issue #155-swingx) - scrollpane policy must be respected. */ @Test public void testCornerNPEVerticalSPOnUpdateUI(){ final JXTable table = new JXTable(10, 2); final JScrollPane scrollPane = new JScrollPane(table); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); table.setColumnControlVisible(true); table.updateUI(); table.setColumnControlVisible(false); assertEquals(JScrollPane.VERTICAL_SCROLLBAR_NEVER, scrollPane.getVerticalScrollBarPolicy()); } /** * Issue #838-swingx: table.prepareRenderer adds bogey listener to column highlighter. * */ @Test public void testColumnHighlighterListener() { JXTable table = new JXTable(10, 2); ColorHighlighter highlighter = new ColorHighlighter(); table.getColumnExt(0).addHighlighter(highlighter); int listenerCount = highlighter.getChangeListeners().length; assertEquals(1, listenerCount); table.prepareRenderer(table.getCellRenderer(0, 0), 0, 0); assertEquals(listenerCount, highlighter.getChangeListeners().length); } /** * Issue #767-swingx: consistent string representation. * * Here: test api on JXTable. */ @Test public void testGetString() { JXTable table = new JXTable(new AncientSwingTeam()); StringValue sv = new StringValue() { @Override 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); } }; table.setDefaultRenderer(Color.class, new DefaultTableRenderer(sv)); String text = table.getStringAt(0, 2); assertEquals(sv.getString(table.getValueAt(0, 2)), text); } @Test public void testCancelEditEnabled() { JXTable table = new JXTable(10, 3); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); assertEquals(table.isEditing(), table.getActionMap().get("cancel").isEnabled()); } @Test public void testCancelEditDisabled() { JXTable table = new JXTable(10, 3); // sanity assertFalse(table.isEditing()); assertEquals(table.isEditing(), table.getActionMap().get("cancel").isEnabled()); } /** * NPE if Generic editor barks about constructor. Hacked around ... * * PENDING JW: too verbose ... strip down to essentials */ @Test public void testGenericEditorNPE() { Date date = new Date(); java.sql.Date sqlDate = new java.sql.Date(date.getTime()); Timestamp stamp = new Timestamp(date.getTime()); Time time = new Time(date.getTime()); DefaultTableModel model = new DefaultTableModel(1, 5) { @Override public Class<?> getColumnClass(int columnIndex) { if (getRowCount() > 0) { Object value = getValueAt(0, columnIndex); if (value != null) { return value.getClass(); } } return super.getColumnClass(columnIndex); } }; model.setColumnIdentifiers(new Object[]{"Date - normal", "SQL Date", "SQL Timestamp", "SQL Time", "Date - as time"}); model.setValueAt(date, 0, 0); model.setValueAt(sqlDate, 0, 1); model.setValueAt(stamp, 0, 2); model.setValueAt(time, 0, 3); model.setValueAt(date, 0, 4); JXTable table = new JXTable(model); table.editCellAt(0, 1); } /** * test that transferFocus methods try to stop edit. * * Here: do nothing if !isTerminateEditOnFocusLost. * */ @Test public void testFocusTransferBackwardTerminateEditFalse() { JXTable table = new JXTable(10, 2); table.setTerminateEditOnFocusLost(false); DefaultCellEditor editor = new DefaultCellEditor(new JTextField()); // need to replace generic editor - which fires twice table.setDefaultEditor(Object.class, editor); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); CellEditorReport report = new CellEditorReport(); table.getCellEditor().addCellEditorListener(report); table.transferFocusBackward(); assertTrue("table must be editing", table.isEditing()); assertEquals("", 0, report.getEventCount()); } /** * test that transferFocus methods try to stop edit. * * Here: do nothing if !isTerminateEditOnFocusLost. * */ @Test public void testFocusTransferForwardTerminateEditFalse() { JXTable table = new JXTable(10, 2); table.setTerminateEditOnFocusLost(false); DefaultCellEditor editor = new DefaultCellEditor(new JTextField()); // need to replace generic editor - which fires twice table.setDefaultEditor(Object.class, editor); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); CellEditorReport report = new CellEditorReport(); table.getCellEditor().addCellEditorListener(report); table.transferFocus(); assertTrue("table must be editing", table.isEditing()); assertEquals("", 0, report.getEventCount()); } /** * test that transferFocus methods try to stop edit. * * Here: respect false on backward. * */ @Test public void testFocusTransferBackwardStopEditingFalse() { JXTable table = new JXTable(10, 2); DefaultCellEditor editor = new DefaultCellEditor(new JTextField()){ @Override public boolean stopCellEditing() { return false; } }; // need to replace generic editor - which fires twice table.setDefaultEditor(Object.class, editor); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); CellEditorReport report = new CellEditorReport(); table.getCellEditor().addCellEditorListener(report); table.transferFocusBackward(); assertTrue("table must be editing", table.isEditing()); assertEquals("", 0, report.getEventCount()); } /** * test that transferFocus methods try to stop edit. * * Here: respect editor false on forward. * */ @Test public void testFocusTransferForwardStopEditingFalse() { JXTable table = new JXTable(10, 2); DefaultCellEditor editor = new DefaultCellEditor(new JTextField()){ @Override public boolean stopCellEditing() { return false; } }; // need to replace generic editor - which fires twice table.setDefaultEditor(Object.class, editor); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); CellEditorReport report = new CellEditorReport(); table.getCellEditor().addCellEditorListener(report); table.transferFocus(); assertTrue("table must be editing", table.isEditing()); assertEquals("", 0, report.getEventCount()); } /** * test that transferFocus methods try to stop edit. * * Here: edit stopped and editor fires on backward. * */ @Test public void testFocusTransferBackwardStopEditing() { JXTable table = new JXTable(10, 2); // need to replace generic editor - which fires twice table.setDefaultEditor(Object.class, new DefaultCellEditor(new JTextField())); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); CellEditorReport report = new CellEditorReport(); table.getCellEditor().addCellEditorListener(report); table.transferFocusBackward(); assertFalse("table must not be editing", table.isEditing()); assertEquals("", 1, report.getEventCount()); assertEquals("", 1, report.getStoppedEventCount()); } /** * test that transferFocus methods try to stop edit. * * Here: edit stopped and editor fired. * */ @Test public void testFocusTransferForwardStopEditing() { JXTable table = new JXTable(10, 2); // need to replace generic editor - which fires twice table.setDefaultEditor(Object.class, new DefaultCellEditor(new JTextField())); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); CellEditorReport report = new CellEditorReport(); table.getCellEditor().addCellEditorListener(report); table.transferFocus(); assertFalse("table must not be editing", table.isEditing()); assertEquals("", 1, report.getEventCount()); assertEquals("", 1, report.getStoppedEventCount()); } /** * test that we have actions registered for forwared/backward * focus transfer. * */ @Test public void testFocusTransferActions() { assertNotNull("must have forward action", table.getActionMap().get(JXTable.FOCUS_NEXT_COMPONENT)); assertNotNull("must have backward action", table.getActionMap().get(JXTable.FOCUS_PREVIOUS_COMPONENT)); } /** * test that we have bindings for forward/backward * focusTransfer. * */ @Test public void testFocusTransferKeyBinding() { JTable core = new JTable(); Set<?> forwardKeys = core.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS); Set<?> backwardKeys = core.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS); for (Object key : forwardKeys) { InputMap map = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); assertNotNull("must have binding for forward focus transfer " + key, map.get((KeyStroke) key)); } for (Object key : backwardKeys) { InputMap map = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); assertNotNull("must have binding for backward focus transfer " + key, map.get((KeyStroke) key)); } } /** * test that we have no focusTransfer keys set. * */ @Test public void testFocusTransferNoDefaultKeys() { assertTrue(table.getFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS).isEmpty()); assertTrue(table.getFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS).isEmpty()); } /** * test that pref scrollable width is updated after structure changed. * */ @Test public void testPrefScrollableUpdatedOnStructureChanged() { JXTable compare = new JXTable(new AncientSwingTeam()); Dimension compareDim = compare.getPreferredScrollableViewportSize(); JXTable table = new JXTable(10, 6); Dimension initialDim = table.getPreferredScrollableViewportSize(); assertFalse("configured must be different from default width", compareDim.width == initialDim.width); table.setModel(compare.getModel()); assertEquals(compareDim.width, table.getPreferredScrollableViewportSize().width); } /** * Issue #508-swingx: cleanup scrollable support. * */ @Test public void testVisibleRowCountUpdateSize() { JXTable table = new JXTable(10, 6); Dimension dim = table.getPreferredScrollableViewportSize(); table.setVisibleRowCount(table.getVisibleRowCount() * 2); // change the pref width of a column, the pref scrollable width must not // be changed. This is testing the table internal reset code. TableColumnExt columnExt = table.getColumnExt(0); columnExt.setPreferredWidth(columnExt.getPreferredWidth() * 2); assertEquals(dim.height * 2, table.getPreferredScrollableViewportSize().height); assertEquals(dim.width, table.getPreferredScrollableViewportSize().width); } /** * Issue #508-swingx: cleanup scrollable support * */ @Test public void testVisibleColumnCountUpdateSize() { JXTable table = new JXTable(10, 14); table.setVisibleColumnCount(6); Dimension dim = table.getPreferredScrollableViewportSize(); table.setVisibleColumnCount(table.getVisibleColumnCount() * 2); assertEquals(dim.width * 2, table.getPreferredScrollableViewportSize().width); assertEquals(dim.height, table.getPreferredScrollableViewportSize().height); } /** * Issue #508-swingx: cleanup pref scrollable size. * test preference of explicit setting (over calculated). * */ @Test public void testPrefScrollableSetPreference() { JXTable table = new JXTable(10, 6); Dimension dim = table.getPreferredScrollableViewportSize(); Dimension other = new Dimension(dim.width + 20, dim.height + 20); table.setPreferredScrollableViewportSize(other); assertEquals(other, table.getPreferredScrollableViewportSize()); } /** * Issue #508-swingx: cleanup pref scrollable size. * test that max number of columns used for the preferred * scrollable width i getVisibleColumnCount * */ @Test public void testPrefScrollableWidthMoreColumns() { JXTable table = new JXTable(10, 7); table.setVisibleColumnCount(6); Dimension dim = table.getPreferredScrollableViewportSize(); // sanity assertEquals(table.getVisibleColumnCount() + 1, table.getColumnCount()); int width = 0; for (int i = 0; i < table.getVisibleColumnCount(); i++) { width += table.getColumn(i).getPreferredWidth(); } assertEquals(width, dim.width); } /** * Issue #508-swingx: cleanup pref scrollable size. * test that max number of columns used for the preferred * scrollable width i getVisibleColumnCount * */ @Test public void testPrefScrollableWidthLessColumns() { JXTable table = new JXTable(10, 5); table.setVisibleColumnCount(6); Dimension dim = table.getPreferredScrollableViewportSize(); // sanity assertEquals(table.getVisibleColumnCount() - 1, table.getColumnCount()); int width = 0; for (int i = 0; i < table.getColumnCount(); i++) { width += table.getColumn(i).getPreferredWidth(); } width += 75; assertEquals(width, dim.width); } /** * test change back to default sizing (use-all) * */ @Test public void testVisibleColumnCountNegative() { JXTable table = new JXTable(10, 7); Dimension dim = table.getPreferredScrollableViewportSize(); int visibleCount = table.getVisibleColumnCount(); // custom table.setVisibleColumnCount(4); // change back to default table.setVisibleColumnCount(visibleCount); assertEquals(dim, table.getPreferredScrollableViewportSize()); } /** * test default sizing: use all visible columns. * */ @Test public void testPrefScrollableWidthDefault() { JXTable table = new JXTable(10, 7); Dimension dim = table.getPreferredScrollableViewportSize(); assertEquals("default must use all visible columns", table.getColumnCount() * 75, dim.width); } /** * Issue #508-swingx: cleanup pref scrollable size. * Sanity: test initial visible column count. * */ @Test public void testDefaultVisibleColumnCount() { JXTable table = new JXTable(10, 6); assertEquals(-1, table.getVisibleColumnCount()); } /** * Issue #508-swingx: cleanup pref scrollable size. * test custom setting of visible column count. * */ @Test public void testVisibleColumnCount() { JXTable table = new JXTable(30, 10); int visibleColumns = 7; table.setVisibleColumnCount(visibleColumns); assertEquals(visibleColumns, table.getVisibleColumnCount()); Dimension dim = table.getPreferredScrollableViewportSize(); assertEquals(visibleColumns * 75, dim.width); } /** * Issue #508-swingx: cleanup pref scrollable size. * test that column widths are configured after setModel. * */ @Test public void testPrefColumnSetModel() { JXTable compare = new JXTable(new AncientSwingTeam()); // make sure the init is called compare.getPreferredScrollableViewportSize(); // table with arbitrary model JXTable table = new JXTable(30, 7); // make sure the init is called table.getPreferredScrollableViewportSize(); // following should init the column width ... table.setModel(compare.getModel()); for (int i = 0; i < table.getColumnCount(); i++) { assertEquals("prefwidths must be same at index " + i, compare.getColumnExt(i).getPreferredWidth(), table.getColumnExt(i).getPreferredWidth()); } } /** * Test bound property visibleRowCount. * */ @Test public void testVisibleRowCountProperty() { JXTable table = new JXTable(10, 7); int count = table.getVisibleRowCount(); PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); table.setVisibleRowCount(count + 1); TestUtils.assertPropertyChangeEvent(report, "visibleRowCount", count, count+1); } /** * Test bound property visibleColumnCount. * */ @Test public void testVisibleColumnCountProperty() { JXTable table = new JXTable(10, 7); int count = table.getVisibleColumnCount(); PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); table.setVisibleColumnCount(count + 1); TestUtils.assertPropertyChangeEvent(report, "visibleColumnCount", count, count+1); } /** * test doc'ed behaviour: set visible row count must * throw on negative row. * */ @Test public void testVisibleRowCountNegative() { JXTable table = new JXTable(10, 7); try { table.setVisibleRowCount(-2); fail("negative count must throw IllegalArgument"); } catch (IllegalArgumentException e) { // expected exception } } /** * Issue #547-swingx: return copy of pref scrollable size * */ @Test public void testPrefScrollableSafeCalculatedDim() { JXTable table = new JXTable(10, 6); // sanity: compare the normal dim returns assertNotSame("pref size must not be the same", table.getPreferredSize(), table.getPreferredSize()); assertNotSame("pref scrollable dim must not be the same", table.getPreferredScrollableViewportSize(), table.getPreferredScrollableViewportSize()); } /** * Issue #547-swingx: return copy of pref scrollable size * This is a super prob - does use the dim as set. */ @Test public void testPrefScrollableSafeFixedDim() { JXTable table = new JXTable(10, 6); Dimension dim = new Dimension(200, 400); // sanity: compare to super prf size when set table.setPreferredSize(dim); assertEquals(dim, table.getPreferredSize()); assertNotSame(dim, table.getPreferredSize()); table.setPreferredScrollableViewportSize(dim); assertNotSame("pref scrollable dim must not be the same", dim, table.getPreferredScrollableViewportSize()); } /** * Issue #547-swingx: pref scrollable height included header. * */ @Test public void testPrefScrollableHeight() { JXTable table = new JXTable(10, 6); Dimension dim = table.getPreferredScrollableViewportSize(); assertNotNull("pref scrollable must not be null", dim); assertEquals("scrollable height must no include header", table.getVisibleRowCount() * table.getRowHeight(), dim.height); } /** * Sanity: default visible row count == 20. * */ @Test public void testDefaultVisibleRowCount() { JXTable table = new JXTable(10, 6); assertEquals(20, table.getVisibleRowCount()); } /** * Issue #547-swingx: NPE in ColumnFactory configureColumnWidth * for hidden column * */ @Test public void testPrefHiddenColumnNPE() { JXTable table = new JXTable(new AncientSwingTeam()); TableColumnExt columnExt = table.getColumnExt(0); columnExt.setPrototypeValue("Jessesmariaandjosefsapperlottodundteufel"); columnExt.setVisible(false); // NPE table.getColumnFactory().configureColumnWidths(table, columnExt); } /** * Issue #547-swingx: NPE in ColumnFactory configureColumnWidth * for empty table .. no: doesn't because * table.getCellRenderer(row,column) does not use the row * coordinate - so the illegal argument doesn't hurt. * */ @Test public void testPrefEmptyTableNPE() { JXTable table = new JXTable(0, 4); TableColumnExt columnExt = table.getColumnExt(0); columnExt.setPrototypeValue("Jessesmariaandjosefsapperlottodundteufel"); // NPE table.getColumnFactory().configureColumnWidths(table, columnExt); } /** * Issue #547-swingx: hidden columns' pref width not initialized. * * PENDING: the default initialize is working as expected only * if the config is done before setting the model, that is * in the ColumnFactory. Need public api to programatically * trigger the init after the fact? */ @Test public void testPrefHiddenColumn() { JXTable table = new JXTable(new AncientSwingTeam()); TableColumnExt columnExt = table.getColumnExt(0); columnExt.setPrototypeValue("Jessesmariaandjosefsapperlottodundteufel"); TableCellRenderer renderer = table.getCellRenderer(0, 0); Component comp = renderer.getTableCellRendererComponent(null, columnExt.getPrototypeValue(), false, false, -1, -1); columnExt.setVisible(false); // make sure the column pref is initialized table.initializeColumnWidths(); assertEquals("hidden column's pref must be set", comp.getPreferredSize().width + table.getColumnMargin(), columnExt.getPreferredWidth()); } /** * Issue #547-swingx: columns' pref width - header not taken * if no prototype * */ @Test public void testPrefColumnTitle() { JXTable table = new JXTable(new AncientSwingTeam()); TableColumnExt columnExt = table.getColumnExt(0); columnExt.setHeaderValue("Jessesmariaandjosefsapperlottodundteufel"); TableCellRenderer renderer = table.getTableHeader().getDefaultRenderer(); Component comp = renderer.getTableCellRendererComponent(table, columnExt.getHeaderValue(), false, false, -1, -1); // need to store the pref - header renderer is used during initialize! Dimension prefSize = comp.getPreferredSize(); // make sure the column pref is initialized table.initializeColumnWidths(); assertEquals("header must be measured", prefSize.width + table.getColumnMargin(), columnExt.getPreferredWidth()); } /** * Issue #547-swingx: columns' pref width - without * prototype the pref is minimally the (magic) standard if * the header doesn't exceed it. * */ @Test public void testPrefStandardMinWithoutPrototype() { JXTable table = new JXTable(10, 6); TableColumnExt columnExt = table.getColumnExt(0); int standardWidth = columnExt.getPreferredWidth(); // make sure the column pref is initialized table.getPreferredScrollableViewportSize(); assertEquals("column pref width must be unchanged", standardWidth, columnExt.getPreferredWidth()); } /** * Issue #547-swingx: columns' pref width - added margin twice * if has prototype. * * PENDING: the default initialize is working as expected only * if the config is done before setting the model, that is * in the ColumnFactory. Need public api to programatically * trigger the init after the fact? */ @Test public void testPrefColumnsDuplicateMargin() { JXTable table = new JXTable(new AncientSwingTeam()); TableColumnExt columnExt = table.getColumnExt(0); // force the prototype longer than the title // to avoid that header measuring is triggered // header renderer can have bigger fonts columnExt.setPrototypeValue(columnExt.getTitle() + "longer"); TableCellRenderer renderer = table.getCellRenderer(0, 0); Component comp = renderer.getTableCellRendererComponent(null, columnExt.getPrototypeValue(), false, false, -1, -1); // make sure the column pref is initialized table.initializeColumnWidths(); assertEquals("column margin must be added once", table.getColumnMargin(), columnExt.getPreferredWidth() - comp.getPreferredSize().width); } /** * core issue: JTable cannot cope with null selection background. * */ @Test public void testSetSelectionBackground() { PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); Color oldBackground = table.getSelectionBackground(); Color color = Color.RED; table.setSelectionBackground(color); assertFalse(oldBackground.equals(table.getSelectionBackground())); assertEquals(color, table.getSelectionBackground()); TestUtils.assertPropertyChangeEvent(report, "selectionBackground", oldBackground, color); } /** * core issue: JTable cannot cope with null selection background. * */ @Test public void testNullSelectionBackground() { table.setSelectionBackground(null); } /** * core issue: JTable cannot cope with null selection background. * */ @Test public void testSetSelectionForeground() { PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); Color oldForeground = table.getSelectionForeground(); Color color = Color.RED; table.setSelectionForeground(color); assertFalse(oldForeground.equals(table.getSelectionForeground())); assertEquals(color, table.getSelectionForeground()); TestUtils.assertPropertyChangeEvent(report, "selectionForeground", oldForeground, color); } /** * core issue: JTable cannot cope with null selection background. * */ @Test public void testNullSelectionForeground() { table.setSelectionForeground(null); } /** * Test default behaviour: hack around DefaultTableCellRenderer * color memory is on. * */ @Test public void testDTCRendererHackEnabled() { JXTable table = new JXTable(10, 2); DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(); table.setDefaultRenderer(Object.class, renderer); table.prepareRenderer(renderer, 0, 0); assertEquals(Boolean.TRUE, table.getClientProperty(JXTable.USE_DTCR_COLORMEMORY_HACK)); assertNotNull(renderer.getClientProperty("rendererColorMemory.background")); assertNotNull(renderer.getClientProperty("rendererColorMemory.foreground")); } /** * Test custom behaviour: hack around DefaultTableCellRenderer * color memory is disabled. * */ @Test public void testDTCRendererHackDisabled() { JXTable table = new JXTable(10, 2); table.putClientProperty(JXTable.USE_DTCR_COLORMEMORY_HACK, null); DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(); table.setDefaultRenderer(Object.class, renderer); table.prepareRenderer(renderer, 0, 0); assertNull(renderer.getClientProperty("rendererColorMemory.background")); assertNull(renderer.getClientProperty("rendererColorMemory.foreground")); } /** * Issue #282-swingx: compare disabled appearance of * collection views. * */ @Test public void testDisabledRenderer() { JXList list = new JXList(new Object[] {"one", "two"}); list.setEnabled(false); // sanity assertFalse(list.isEnabled()); Component comp = list.getCellRenderer().getListCellRendererComponent(list, "some", 0, false, false); assertEquals(list.isEnabled(), comp.isEnabled()); JXTable table = new JXTable(10, 2); table.setEnabled(false); // sanity assertFalse(table.isEnabled()); comp = table.prepareRenderer(table.getCellRenderer(0, 0), 0, 0); assertEquals(table.isEnabled(), comp.isEnabled()); } /** * Issue 372-swingx: table must cancel edit if column property * changes to not editable. * Here we test if the table actually canceled the edit. */ @Test public void testTableCanceledEditOnColumnEditableChange() { JXTable table = new JXTable(10, 2); TableColumnExt columnExt = table.getColumnExt(0); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); assertEquals(0, table.getEditingColumn()); TableCellEditor editor = table.getCellEditor(); CellEditorReport report = new CellEditorReport(); editor.addCellEditorListener(report); columnExt.setEditable(false); // sanity assertFalse(table.isCellEditable(0, 0)); assertEquals("editor must have fired canceled", 1, report.getCanceledEventCount()); assertEquals("editor must not have fired stopped",0, report.getStoppedEventCount()); } /** * Issue 372-swingx: table must cancel edit if column property * changes to not editable. * Here we test if the table is not editing after editable property * of the currently edited column is changed to false. */ @Test public void testTableNotEditingOnColumnEditableChange() { JXTable table = new JXTable(10, 2); TableColumnExt columnExt = table.getColumnExt(0); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); assertEquals(0, table.getEditingColumn()); columnExt.setEditable(false); assertFalse(table.isCellEditable(0, 0)); assertFalse("table must have terminated edit",table.isEditing()); } /** * Issue 372-swingx: table must cancel edit if column property * changes to not editable. * Here we test if the table is still editing after the editability * change of a non-edited column. * */ @Test public void testTableEditingOnNotEditingColumnEditableChange() { JXTable table = new JXTable(10, 2); int notEditingColumn = 1; TableColumnExt columnExt = table.getColumnExt(notEditingColumn); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); assertEquals(0, table.getEditingColumn()); columnExt.setEditable(false); assertFalse(table.isCellEditable(0, notEditingColumn)); assertTrue("table must still be editing", table.isEditing()); } /** * Issue 372-swingx: table must cancel edit if column property * changes to not editable. * Here we test if the table is still editing after the editability * change of a non-edited column, special case of hidden column. <p> * NOTE: doesn't really test, the columnModel doesn't * fire propertyChanges for hidden columns (see Issue #??-swingx) * */ @Test public void testTableEditingOnHiddenColumnEditableChange() { JXTable table = new JXTable(10, 2); int hiddenNotEditingColumn = 1; TableColumnExt columnExt = table.getColumnExt(hiddenNotEditingColumn); columnExt.setVisible(false); table.editCellAt(0, 0); // sanity assertTrue(table.isEditing()); assertEquals(0, table.getEditingColumn()); columnExt.setEditable(false); assertTrue("table must still be editing", table.isEditing()); } /** * Test if default column creation and configuration is * controlled completely by ColumnFactory. * */ @Test public void testColumnConfigControlledByFactory() { ColumnFactory factory = new ColumnFactory() { @Override public void configureTableColumn(TableModel model, TableColumnExt columnExt) { assertNull(columnExt.getHeaderValue()); } }; table.setColumnFactory(factory); table.setModel(new DefaultTableModel(10, 2)); assertEquals(null, table.getColumn(0).getHeaderValue()); } /** * Sanity test for cleanup of createDefaultColumns. * */ @Test public void testColumnFactory() { JXTable table = new JXTable(sortableTableModel); List<TableColumn> columns = table.getColumns(); // for all model columns and in same order.. assertEquals(sortableTableModel.getColumnCount(), columns.size()); for (int i = 0; i < sortableTableModel.getColumnCount(); i++) { // there must have been inserted a TableColumnExt with // title == headerValue == column name in model assertTrue(columns.get(i) instanceof TableColumnExt); assertEquals(sortableTableModel.getColumnName(i), String.valueOf(columns.get(i).getHeaderValue())); } } /** * Tests per-table ColumnFactory: bound property, reset to shared. * */ @Test public void testSetColumnFactory() { PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); ColumnFactory factory = createCustomColumnFactory(); table.setColumnFactory(factory); assertEquals(1, report.getEventCount()); assertTrue(report.hasEvents("columnFactory")); assertSame(factory, report.getLastNewValue("columnFactory")); assertSame(ColumnFactory.getInstance(), report.getLastOldValue("columnFactory")); report.clear(); table.setColumnFactory(null); assertEquals(1, report.getEventCount()); assertTrue(report.hasEvents("columnFactory")); assertSame(factory, report.getLastOldValue("columnFactory")); assertSame(ColumnFactory.getInstance(), report.getLastNewValue("columnFactory")); } /** * Tests per-table ColumnFactory: use individual. * */ @Test public void testUseCustomColumnFactory() { PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); ColumnFactory factory = createCustomColumnFactory(); table.setColumnFactory(factory); // sanity... assertSame(factory, report.getLastNewValue("columnFactory")); table.setModel(new DefaultTableModel(2, 5)); assertEquals(String.valueOf(0), table.getColumnExt(0).getTitle()); } /** * Creates and returns a custom columnFactory for testing. * Sets column title to modelIndex. * * @return the custom ColumnFactory. */ protected ColumnFactory createCustomColumnFactory() { ColumnFactory factory = new ColumnFactory() { @Override public void configureTableColumn(TableModel model, TableColumnExt columnExt) { super.configureTableColumn(model, columnExt); columnExt.setTitle(String.valueOf(columnExt.getModelIndex())); } }; return factory; } /** * Issue #4614616: renderer lookup broken for interface types. * */ @Test public void testNPERendererForInterface() { DefaultTableModel model = new DefaultTableModel(10, 2) { @Override public Class<?> getColumnClass(int columnIndex) { return Comparable.class; } }; JXTable table = new JXTable(model); table.prepareRenderer(table.getCellRenderer(0, 0), 0, 0); } /** * Issue #366-swingx: enhance generic editor to take custom * textfield as argument. * */ @Test public void testGenericEditor() { JTextField textField = new JTextField(20); GenericEditor editor = new GenericEditor(textField); assertEquals("Table.editor", textField.getName()); // sanity assertSame(textField, editor.getComponent()); } /** * test default rowHeight calculation with default font. * Beware: the default height is the font's height + 2, but * bounded by a "magic" minimum of 18. */ @Test public void testRowHeightFromFont() { // sanity assertNull("no ui rowheight", UIManager.get("JXTable.rowHeight")); JXTable table = new JXTable(); // wrong assumption: there's a "magic" minimum of 18! int fontDerivedHeight = table.getFontMetrics(table.getFont()).getHeight() + 2; assertEquals("default rowHeight based on fontMetrics height " + "plus top plus bottom border (== 2)", Math.max(18, fontDerivedHeight), table.getRowHeight()); } /** * test default rowHeight calculation with bigger font. * Issue #1149-swingx: this test failed on Mac. * * "expected 17, was 18" - running into the magic number (for * whatever reason), which wasn't accounted for. Changed to backout * if font not really changed to big. * */ @Test public void testRowHeightFromBigFont() { // sanity assertNull("no ui rowheight", UIManager.get("JXTable.rowHeight")); JXTable table = new JXTable(); table.setFont(table.getFont().deriveFont(table.getFont().getSize() * 2f)); table.updateUI(); int bigRowHeight = table.getFontMetrics(table.getFont()).getHeight() + 2; if (bigRowHeight <= 18) { LOG.info("can't test - font not changed to big as expected but still " + bigRowHeight); return; } assertEquals("default rowHeight based on fontMetrics height " + "plus top plus bottom border (== 2)", bigRowHeight, table.getRowHeight()); } /** * Issue #359-swingx: table doesn't respect ui-setting of rowheight. * * lower bound is enforced to "magic number", 18 * */ @Test public void testUIRowHeightLowerBound() { int tinyRowHeight = 5; UIManager.put("JXTable.rowHeight", tinyRowHeight); JXTable table = new JXTable(); assertEquals("table must respect ui rowheight", tinyRowHeight, table.getRowHeight()); } /** * Issue #359-swingx: table doesn't respect ui-setting of rowheight. * * upper bound is taken correctly. */ @Test public void testUIRowHeightUpperBound() { int monsterRowHeight = 50; UIManager.put("JXTable.rowHeight", monsterRowHeight); JXTable table = new JXTable(); assertEquals("table must respect ui rowheight", monsterRowHeight, table.getRowHeight()); } /** * Issue #342-swingx: default margins in JXTreeTable. * * This is not only a treeTable issue: the coupling of * margins to showing gridlines (and still get a "nice" * looking selection) is not overly obvious in JTable as * well. Added convenience method to adjust margins to * 0/1 if hiding/showing grid lines. * */ @Test public void testShowGrid() { JXTable table = new JXTable(10, 3); // sanity: initial margins are (1, 1), grid on assertEquals(1, table.getRowMargin()); assertTrue(table.getShowHorizontalLines()); assertEquals(1, table.getColumnMargin()); assertTrue(table.getShowVerticalLines()); // hide grid boolean show = false; table.setShowGrid(show, show); assertEquals(0, table.getRowMargin()); assertEquals(show, table.getShowHorizontalLines()); assertEquals(0, table.getColumnMargin()); assertEquals(show, table.getShowVerticalLines()); } /** * Issue ??-swingx: NPE if tableChanged is messaged with a null event. * */ @Test public void testNullTableEventNPE() { // don't throw null events table.tableChanged(null); assertFalse(table.isUpdate(null)); assertFalse(table.isDataChanged(null)); assertTrue(table.isStructureChanged(null)); // correct detection of structureChanged TableModelEvent structureChanged = new TableModelEvent(table.getModel(), -1, -1); assertFalse(table.isUpdate(structureChanged)); assertFalse(table.isDataChanged(structureChanged)); assertTrue(table.isStructureChanged(structureChanged)); // correct detection of insert/remove TableModelEvent insert = new TableModelEvent(table.getModel(), 0, 10, -1, TableModelEvent.INSERT); assertFalse(table.isUpdate(insert)); assertFalse(table.isDataChanged(insert)); assertFalse(table.isStructureChanged(insert)); // correct detection of update TableModelEvent update = new TableModelEvent(table.getModel(), 0, 10); assertTrue(table.isUpdate(update)); assertFalse(table.isDataChanged(update)); assertFalse(table.isStructureChanged(update)); // correct detection of dataChanged TableModelEvent dataChanged = new TableModelEvent(table.getModel()); assertFalse(table.isUpdate(dataChanged)); assertTrue(table.isDataChanged(dataChanged)); assertFalse(table.isStructureChanged(dataChanged)); } /** * test new mutable columnControl api. * */ @Test public void testSetColumnControl() { JComponent columnControl = table.getColumnControl(); assertTrue(columnControl instanceof ColumnControlButton); JComponent newControl = new JButton(); PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); table.setColumnControl(newControl); assertSame(newControl, table.getColumnControl()); assertEquals(1, report.getEventCount()); assertEquals(1, report.getEventCount("columnControl")); assertSame(newControl, report.getLastNewValue("columnControl")); } /** * characterization tests: constructors and exceptions. * */ @Test public void testConstructorsWithNullArguments() { try { new JXTable((Object[][]) null, (Object[]) null); fail("null arrays must throw NPE"); } catch (NullPointerException e) { // nothing to do - expected } catch (Exception e) { fail("unexpected exception type (expected NPE)" + e); } try { new JXTable((Object[][]) null, new Object[] { }); fail("null arrays must throw NPE"); } catch (NullPointerException e) { // nothing to do - expected } catch (Exception e) { fail("unexpected exception type (expected NPE)" + e); } try { new JXTable(new Object[][] {{ }, { }}, (Object[]) null); fail("null arrays throw NPE"); } catch (NullPointerException e) { // nothing to do - expected } catch (Exception e) { fail("unexpected exception type (expected NPE)" + e); } } /** * expose JTable.autoStartsEdit. * */ @Test public void testAutoStartEdit() { JXTable table = new JXTable(10, 2); assertTrue(table.isAutoStartEditOnKeyStroke()); PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); table.setAutoStartEditOnKeyStroke(false); assertFalse("autoStart must be toggled to false", table.isAutoStartEditOnKeyStroke()); // the following assumption is wrong because the old client property key is // different from the method name, leading to two events fired. // assertEquals(1, report.getEventCount()); assertEquals(1, report.getEventCount("autoStartEditOnKeyStroke")); } /** * add editable property. * */ @Test public void testEditable() { JXTable table = new JXTable(10, 2); assertTrue("default editable must be true", table.isEditable()); PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); table.setEditable(!table.isEditable()); assertFalse("editable must be toggled to false", table.isEditable()); assertEquals(1, report.getEventCount()); assertEquals(1, report.getEventCount("editable")); } /** * test effect of editable on cell editing. * */ @Test public void testCellEditable() { JXTable table = new JXTable(10, 2); assertTrue("default table editable must be true", table.isEditable()); assertTrue("default cell editable must be true", table.isCellEditable(0, 0)); table.setEditable(!table.isEditable()); assertFalse("editable must be toggled to false", table.isEditable()); assertFalse("each cell must be not editable", table.isCellEditable(0, 0)); } /** * */ @Test public void testSetValueCellNotEditable() { JXTable table = new JXTable(10, 2); Object value = table.getValueAt(0, 0); table.setEditable(false); // sanity... assertFalse("each cell must be not editable", table.isCellEditable(0, 0)); table.setValueAt("wrong", 0, 0); assertEquals("cell value must not be changed", value, table.getValueAt(0, 0)); } /** * Issue #262-swingx: expose terminateEditOnFocusLost as property. * * setting client property is reflected in getter and results in event firing. * */ @Test public void testGetTerminateEditOnFocusLost() { // sanity assert: setting client property set's property PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); table.putClientProperty("terminateEditOnFocusLost", !table.isTerminateEditOnFocusLost()); assertEquals(table.getClientProperty("terminateEditOnFocusLost"), table.isTerminateEditOnFocusLost()); assertEquals(1, report.getEventCount()); assertEquals(1, report.getEventCount("terminateEditOnFocusLost")); } /** * Issue #262-swingx: expose terminateEditOnFocusLost as property. * * default value is true. * */ @Test public void testInitialTerminateEditOnFocusLost() { assertTrue("terminate edit must be on by default", table.isTerminateEditOnFocusLost()); } /** * Issue #262-swingx: expose terminateEditOnFocusLost as property. * * setter is same as setting client property and results in event firing. */ @Test public void testSetTerminateEditOnFocusLost() { // sanity assert: setting client property set's property PropertyChangeReport report = new PropertyChangeReport(); table.addPropertyChangeListener(report); table.setTerminateEditOnFocusLost(!table.isTerminateEditOnFocusLost()); assertEquals(table.getClientProperty("terminateEditOnFocusLost"), table.isTerminateEditOnFocusLost()); assertEquals(1, report.getEventCount()); assertEquals(1, report.getEventCount("terminateEditOnFocusLost")); assertEquals(Boolean.FALSE, report.getLastNewValue("terminateEditOnFocusLost")); } /** * sanity test while cleaning up: * getColumns() should return the exact same * ordering as getColumns(false); * */ @Test public void testColumnSequence() { JXTable table = new JXTable(10, 20); table.getColumnExt(5).setVisible(false); table.getColumnModel().moveColumn(table.getColumnCount() - 1, 0); assertEquals(table.getColumns(), table.getColumns(false)); } /** * Issue #256-swingx: added fillsViewportHeight property. * * check "fillsViewportHeight" property change fires event. * */ @Test public void testDefaultFillsViewport() { JXTable table = new JXTable(10, 1); boolean fill = table.getFillsViewportHeight(); assertTrue("fillsViewport is on by default", fill); } /** * Issue #256-swingX: viewport - don't change background * in configureEnclosingScrollPane. * * */ @Test public void testUnchangedViewportBackground() { JXTable table = new JXTable(10, 2); JScrollPane scrollPane = new JScrollPane(); scrollPane.setSize(500, 500); Color viewportColor = scrollPane.getViewport().getBackground(); Color tableColor = table.getBackground(); if ((viewportColor != null) && viewportColor.equals(tableColor)) { LOG.info("cannot run test unchanged viewport background because \n" + "viewport has same color as table. \n" + "viewport: " + viewportColor + "\n table: " + tableColor); return; } scrollPane.setViewportView(table); table.configureEnclosingScrollPane(); assertEquals("viewport background must be unchanged", viewportColor, scrollPane.getViewport().getBackground()); } /** * Issue #214-swingX: improved auto-resize. * * */ @Test public void testTrackViewportWidth() { // This test will not work in a headless configuration. if (GraphicsEnvironment.isHeadless()) { LOG.fine("cannot run trackViewportWidth - headless environment"); return; } JXTable table = new JXTable(10, 2); table.setHorizontalScrollEnabled(true); Dimension tablePrefSize = table.getPreferredSize(); JScrollPane scrollPane = new JScrollPane(table); JXFrame frame = wrapInFrame(scrollPane, ""); frame.setSize(tablePrefSize.width * 2, tablePrefSize.height); frame.setVisible(true); assertEquals("table width must be equal to viewport", table.getWidth(), scrollPane.getViewport().getWidth()); } /** * Issue #214-swingX: improved auto-resize. * * */ @Test public void testSetHorizontalEnabled() { JXTable table = new JXTable(10, 2); table.setHorizontalScrollEnabled(true); assertTrue("enhanced resize property must be enabled", table.isHorizontalScrollEnabled()); assertHorizontalActionSelected(table, true); } private void assertHorizontalActionSelected(JXTable table, boolean selected) { Action showHorizontal = table.getActionMap().get( JXTable.HORIZONTALSCROLL_ACTION_COMMAND); assertEquals("horizontAction must be selected" , selected, ((BoundAction) showHorizontal).isSelected()); } /** * Issue #214-swingX: improved auto-resize. * test autoResizeOff != intelliResizeOff * after sequence a) set intelli, b) setAutoResize * */ @Test public void testNotTrackViewportWidth() { // This test will not work in a headless configuration. if (GraphicsEnvironment.isHeadless()) { LOG.fine("cannot run trackViewportWidth - headless environment"); return; } JXTable table = new JXTable(10, 2); table.setHorizontalScrollEnabled(true); table.setAutoResizeMode(JXTable.AUTO_RESIZE_OFF); Dimension tablePrefSize = table.getPreferredSize(); JScrollPane scrollPane = new JScrollPane(table); JXFrame frame = wrapInFrame(scrollPane, ""); frame.setSize(tablePrefSize.width * 2, tablePrefSize.height); frame.setVisible(true); assertEquals("table width must not be equal to viewport", table.getPreferredSize().width, table.getWidth()); } /** * Issue #214-swingX: improved auto-resize. * test autoResizeOff != intelliResizeOff * */ @Test public void testAutoResizeOffNotHorizontalScrollEnabled() { JXTable table = new JXTable(10, 2); table.setAutoResizeMode(JXTable.AUTO_RESIZE_OFF); // sanity: horizontal action must be selected assertHorizontalActionSelected(table, false); assertFalse("autoResizeOff must not enable enhanced resize", table.isHorizontalScrollEnabled()); } /** * Issue #214-swingX: improved auto-resize. * * testing doc'd behaviour: horizscrollenabled toggles between * enhanced resizeOff and the resizeOn mode which had been active * when toggling on. * */ @Test public void testOldAutoResizeOn() { JXTable table = new JXTable(10, 2); int oldAutoResize = table.getAutoResizeMode(); table.setHorizontalScrollEnabled(true); table.setHorizontalScrollEnabled(false); assertEquals("old on-mode must be restored", oldAutoResize, table.getAutoResizeMode()); } /** * Issue #214-swingX: improved auto-resize. * * testing doc'd behaviour: horizscrollenabled toggles between * enhanced resizeOff and the resizeOn mode which had been active * when toggling on. Must not restore raw resizeOff mode. * * */ @Test public void testNotOldAutoResizeOff() { JXTable table = new JXTable(10, 2); int oldAutoResize = table.getAutoResizeMode(); table.setAutoResizeMode(JXTable.AUTO_RESIZE_OFF); table.setHorizontalScrollEnabled(true); table.setHorizontalScrollEnabled(false); assertEquals("old on-mode must be restored", oldAutoResize, table.getAutoResizeMode()); } /** * Issue #214-swingX: improved auto-resize. * test autoResizeOff != intelliResizeOff * after sequence a) set intelli, b) setAutoResize * */ @Test public void testAutoResizeOffAfterHorizontalScrollEnabled() { JXTable table = new JXTable(10, 2); table.setHorizontalScrollEnabled(true); // sanity: intelliResizeOff enabled assertTrue(table.isHorizontalScrollEnabled()); // sanity: horizontal action must be selected assertHorizontalActionSelected(table, true); table.setAutoResizeMode(JXTable.AUTO_RESIZE_OFF); assertFalse("autoResizeOff must not enable enhanced resize", table.isHorizontalScrollEnabled()); // sanity: horizontal action must be selected assertHorizontalActionSelected(table, false); } /** * Issue 252-swingx: getColumnExt throws ClassCastException if tableColumn * is not of type TableColumnExt. * */ @Test public void testTableColumnType() { table.setAutoCreateColumnsFromModel(false); table.setModel(new DefaultTableModel(2, 1)); TableColumnModel columnModel = new DefaultTableColumnModel(); columnModel.addColumn(new TableColumn(0)); table.setColumnModel(columnModel); // valid column index must not throw exception TableColumnExt tableColumnExt = table.getColumnExt(0); assertNull("getColumnExt must return null on type mismatch", tableColumnExt); } /** * test contract: getColumnExt(int) throws ArrayIndexOutofBounds with * invalid column index. * */ @Test public void testTableColumnExtOffRange() { JXTable table = new JXTable(2, 1); try { table.getColumnExt(1); fail("accessing invalid column index must throw ArrayIndexOutofBoundExc"); } catch (ArrayIndexOutOfBoundsException e) { // do nothing: contracted runtime exception } catch (Exception e) { fail("unexpected exception: " + e + "\n" + "accessing invalid column index must throw ArrayIndexOutofBoundExc"); } } /** * test contract: getColumn(int) throws ArrayIndexOutofBounds with * invalid column index.<p> * * Subtle autoboxing issue: * JTable has convenience method getColumn(Object) to access by * identifier, but doesn't have delegate method to columnModel.getColumn(int) * Clients assuming the existence of a direct delegate no longer get a * compile-time error message in 1.5 due to autoboxing. * Furthermore, the runtime exception is unexpected (IllegalArgument * instead of AIOOB). <p> * * Added getColumn(int) to JXTable api to solve. * */ @Test public void testTableColumnOffRange() { JXTable table = new JXTable(2, 1); try { table.getColumn(1); fail("accessing invalid column index must throw ArrayIndexOutofBoundExc"); } catch (ArrayIndexOutOfBoundsException e) { // do nothing: contracted runtime exception } catch (Exception e) { fail("unexpected exception: " + e + "\n" + "accessing invalid column index must throw ArrayIndexOutofBoundExc"); } } /** * Issue #251-swingx: JXTable doesn't respect TableColumn editability. * report, test and fix by nicfagn (Nicola Fagnani), * */ @Test public void testTableColumnEditable() { DefaultTableModel model = new DefaultTableModel( 2, 2 ); JXTable table = new JXTable( model ); // DefaultTableModel allows to edit its cells. for( int i = 0; i < model.getRowCount(); i++ ) { for( int j = 0; j < model.getRowCount(); j++ ) { assertEquals( "cell (" + i + "," + j + ") must be editable", true, table.isCellEditable( i, j ) ); } } // First column not editable. int column = 0; table.getColumnExt( column ).setEditable( false ); for( int i = 0; i < model.getRowCount(); i++ ) { for( int j = 0; j < model.getRowCount(); j++ ) { assertEquals( "cell (" + i + "," + j + ") must " + (j == column ? "not" : "") + " be editable", !(j == column), table.isCellEditable( i, j ) ); } } table.getColumnExt( column ).setEditable( true ); // Second column not editable. column = 1; table.getColumnExt( column ).setEditable( false ); for( int i = 0; i < model.getRowCount(); i++ ) { for( int j = 0; j < model.getRowCount(); j++ ) { assertEquals( "cell (" + i + "," + j + ") must " + (j == column ? "not" : "") + " be editable", !(j == column), table.isCellEditable( i, j ) ); } } table.getColumnExt( column ).setEditable( true ); } /** * test if LinkController/executeButtonAction is properly registered/unregistered on * setRolloverEnabled. * */ @Test public void testLinkControllerListening() { JXTable table = new JXTable(); 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(RolloverController.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(RolloverController.EXECUTE_BUTTON_ACTIONCOMMAND)); } private PropertyChangeListener getLinkControllerAsPropertyChangeListener(JXTable table, String propertyName) { PropertyChangeListener[] listeners = table.getPropertyChangeListeners(propertyName); for (int i = 0; i < listeners.length; i++) { if (listeners[i] instanceof TableRolloverController<?>) { return (TableRolloverController<?>) listeners[i]; } } return null; } /** * Issue #180-swingx: outOfBoundsEx if testColumn is hidden. * */ @Test public void testHighlighterHiddenTestColumn() { JXTable table = new JXTable(sortableTableModel); table.getColumnExt(0).setVisible(false); Highlighter highlighter = new ColorHighlighter(new PatternPredicate("a", 0), null, Color.RED); ComponentAdapter adapter = table.getComponentAdapter(); adapter.row = 0; adapter.column = 0; highlighter.highlight(new JLabel(), adapter); } /** * Issue #54: hidden columns not removed on setModel. * */ @Test public void testRemoveAllColumsAfterModelChanged() { JXTable table = new JXTable(sortableTableModel); TableColumnExt columnX = table.getColumnExt(0); columnX.setVisible(false); table.setModel(new DefaultTableModel()); assertEquals("all columns must have been removed", 0, table.getColumnCount(true)); assertEquals("all columns must have been removed", table.getColumnCount(), table.getColumnCount(true)); } /** * Issue #165-swingx: IllegalArgumentException when * hiding/reshowing columns "at end" of column model. * */ @Test public void testHideShowLastColumns() { JXTable table = new JXTable(10, 3); TableColumnExt ext = table.getColumnExt(2); for (int i = table.getModel().getColumnCount() - 1; i > 0; i--) { table.getColumnExt(i).setVisible(false); } ext.setVisible(true); } /** * Issue #155-swingx: lost setting of initial scrollBarPolicy. * */ @Test public void testConserveVerticalScrollBarPolicy() { // This test will not work in a headless configuration. if (GraphicsEnvironment.isHeadless()) { LOG.fine("cannot run conserveVerticalScrollBarPolicy - headless environment"); return; } JXTable table = new JXTable(0, 3); JScrollPane scrollPane1 = new JScrollPane(table); scrollPane1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); JXFrame frame = new JXFrame(); frame.add(scrollPane1); frame.setSize(500, 400); frame.setVisible(true); assertEquals("vertical scrollbar policy must be always", JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, scrollPane1.getVerticalScrollBarPolicy()); } /** * Issue #197: JXTable pattern search differs from * PatternHighlighter/Filter. * */ @Test public void testRespectPatternInSearch() { JXTable table = new JXTable(createAscendingModel(0, 11)); int row = 1; String lastName = table.getValueAt(row, 0).toString(); Pattern strict = Pattern.compile("^" + lastName + "$"); int found = table.getSearchable().search(strict, -1, false); assertEquals("found must be equal to row", row, found); found = table.getSearchable().search(strict, found, false); assertEquals("search must fail", -1, found); } /** * Issue #54-swingx: hidden columns not removed. * */ @Test public void testRemoveAllColumns() { JXTable table = new JXTable(sortableTableModel); TableColumnExt columnX = table.getColumnExt(0); columnX.setVisible(false); // set empty model table.setModel(new DefaultTableModel(0, 0)); assertEquals("all columns must have been removed", table.getColumnCount(), table.getColumnCount(true)); } /** * testing contract of getColumnExt. * */ @Test public void testColumnExt() { JXTable table = new JXTable(sortableTableModel); /// arrgghhh... autoboxing ? // Object zeroName = table.getColumn(0).getIdentifier(); Object zeroName = table.getColumnModel().getColumn(0).getIdentifier(); Object oneName = table.getColumnModel().getColumn(1).getIdentifier(); TableColumn column = table.getColumn(zeroName); ((TableColumnExt) column).setVisible(false); try { // access the invisible column by the inherited method table.getColumn(zeroName); fail("table.getColumn(identifier) guarantees to fail if identifier " + "is unknown or column is hidden"); } catch (Exception e) { // this is what we expect } // access the invisible column by new method TableColumnExt columnZero = table.getColumnExt(zeroName); // sanity.. assertNotNull(columnZero); int viewIndexZero = table.convertColumnIndexToView(columnZero .getModelIndex()); assertTrue("viewIndex must be negative for invisible", viewIndexZero < 0); // a different way to state the same assertEquals(columnZero.isVisible(), viewIndexZero >= 0); TableColumnExt columnOne = table.getColumnExt(oneName); // sanity.. assertNotNull(columnOne); int viewIndexOne = table.convertColumnIndexToView(columnOne .getModelIndex()); assertTrue("viewIndex must be positive for visible", viewIndexOne >= 0); assertEquals(columnOne.isVisible(), viewIndexOne >= 0); } /** * Issue #189, #214: Sorter fails if content is comparable with mixed types * */ @Test public void testMixedComparableTypes() { Object[][] rowData = new Object[][] { new Object[] { Boolean.TRUE, new Integer(2) }, new Object[] { Boolean.TRUE, "BC" } }; String[] columnNames = new String[] { "Critical", "Task" }; DefaultTableModel model = new DefaultTableModel(rowData, columnNames); final JXTable table = new JXTable(model); table.toggleSortOrder(1); } /** * Issue #189, #214: Sorter fails if content is * mixed comparable/not comparable * */ @Test public void testMixedComparableTypesWithNonComparable() { Object[][] rowData = new Object[][] { new Object[] { Boolean.TRUE, new Integer(2) }, new Object[] { Boolean.TRUE, new Object() } }; String[] columnNames = new String[] { "Critical", "Task" }; DefaultTableModel model = new DefaultTableModel(rowData, columnNames); final JXTable table = new JXTable(model); table.toggleSortOrder(1); } @Test public void testIncrementalSearch() { JXTable table = new JXTable(createAscendingModel(10, 10)); int row = 0; String ten = table.getValueAt(row, 0).toString(); // sanity assert assertEquals("10", ten); int found = table.getSearchable().search("1", -1); assertEquals("must have found first row", row, found); int second = table.getSearchable().search("10", found); assertEquals("must have found incrementally at same position", found, second); } /** * Issue #196: backward search broken. * */ @Test public void testBackwardSearch() { JXTable table = new JXTable(createAscendingModel(0, 10)); int row = 1; String lastName = table.getValueAt(row, 0).toString(); int found = table.getSearchable().search(Pattern.compile(lastName), -1, true); assertEquals(row, found); } /** * Issue #174: componentAdapter.hasFocus() looks for anchor instead of lead. * @throws InvocationTargetException * @throws InterruptedException * */ @Test public void testLeadFocusCell() throws InterruptedException, InvocationTargetException { // This test will not work in a headless configuration. if (GraphicsEnvironment.isHeadless()) { LOG.fine("cannot run leadFocusCell - headless environment"); return; } final JXTable table = new JXTable(); table.setModel(createAscendingModel(0, 10)); final JXFrame frame = new JXFrame(); frame.add(table); frame.pack(); frame.setVisible(true); table.requestFocus(); table.addRowSelectionInterval(table.getRowCount() - 2, table.getRowCount() - 1); final int leadRow = table.getSelectionModel().getLeadSelectionIndex(); int anchorRow = table.getSelectionModel().getAnchorSelectionIndex(); table.addColumnSelectionInterval(0, 0); final int leadColumn = table.getColumnModel().getSelectionModel().getLeadSelectionIndex(); int anchorColumn = table.getColumnModel().getSelectionModel().getAnchorSelectionIndex(); assertEquals("lead must be last row", table.getRowCount() - 1, leadRow); assertEquals("anchor must be second last row", table.getRowCount() - 2, anchorRow); assertEquals("lead must be first column", 0, leadColumn); assertEquals("anchor must be first column", 0, anchorColumn); // take a nap to make sure we are created before testing for focus on linux // w/o this the test will intermittently fail on systems using kernel 2.6 and sun java 6 Thread.sleep(500); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { ComponentAdapter adapter = table.getComponentAdapter(); adapter.row = leadRow; adapter.column = leadColumn; // difficult to test - hasFocus() implies that the table isFocusOwner() try { assertTrue("adapter must have focus for leadRow/Column: " + adapter.row + "/" + adapter.column, adapter.hasFocus()); } finally { frame.dispose(); } } }); } /** * Test default editors types as expected. * */ @Test public void testLazyEditorsByClass() { JXTable table = new JXTable(); assertEquals("default Boolean editor", JXTable.BooleanEditor.class, table.getDefaultEditor(Boolean.class).getClass()); assertEquals("default Number editor", NumberEditorExt.class, table.getDefaultEditor(Number.class).getClass()); assertEquals("default Double editor", NumberEditorExt.class, table.getDefaultEditor(Double.class).getClass()); } /** * Convenience: type cast of default rowSorter. * @param table * @return */ private static TableSortController<? extends TableModel> getSortController(JXTable table) { return (TableSortController<? extends TableModel>) table.getRowSorter(); } //---------------------------- factory, convenience models, set-up ... /** * returns a tableModel with count rows filled with * ascending integers in first column * starting from startRow. * @param startRow the value of the first row * @param count the number of rows * @return */ protected DefaultTableModel createAscendingModel(int startRow, int count) { DefaultTableModel model = new DefaultTableModel(count, 4) { @Override public Class<?> getColumnClass(int column) { return column == 0 ? Integer.class : super.getColumnClass(column); } }; for (int i = 0; i < model.getRowCount(); i++) { model.setValueAt(new Integer(startRow++), i, 0); } return model; } /** * returns a tableModel with count rows filled with * ascending integers in first/last column depending on fillLast * starting from startRow. * with columnCount columns * @param startRow the value of the first row * @param rowCount the number of rows * @param columnCount the number of columns * @param fillLast boolean to indicate whether to ill the value in the first * or last column * @return a configured DefaultTableModel. */ protected DefaultTableModel createAscendingModel(int startRow, final int rowCount, final int columnCount, boolean fillLast) { DefaultTableModel model = new DefaultTableModel(rowCount, columnCount) { @Override public Class<?> getColumnClass(int column) { Object value = rowCount > 0 ? getValueAt(0, column) : null; return value != null ? value.getClass() : super.getColumnClass(column); } }; int filledColumn = fillLast ? columnCount - 1 : 0; for (int i = 0; i < model.getRowCount(); i++) { model.setValueAt(new Integer(startRow++), i, filledColumn); } return model; } //----------------------- test data for exposing #171 (Tim Dilks) /** * test object to map in test table model. */ static class RowObject { private String data1; private boolean editable; public RowObject(String data1, boolean editable) { this.data1 = data1; this.editable = editable; } public String getData1() { return data1; } public boolean isEditable() { return editable; } } /** * test TableModel wrapping RowObject. */ static class RowObjectTableModel extends AbstractTableModel { List<?> data; public RowObjectTableModel(List<?> data) { this.data = data; } public RowObject getRowObject(int row) { return (RowObject) data.get(row); } @Override public int getColumnCount() { return 2; } @Override public int getRowCount() { return data.size(); } @Override public Object getValueAt(int row, int col) { RowObject object = getRowObject(row); switch (col) { case 0 : return object.getData1(); case 1 : return object.isEditable() ? "EDITABLE" : "NOT EDITABLE"; default : return null; } } @Override public boolean isCellEditable(int row, int col) { return getRowObject(row).isEditable(); } } /** * 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() { @Override 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; } public static class DynamicTableModel extends AbstractTableModel { private Object columnSamples[]; private Object columnSamples2[]; public URL linkURL; public static final int IDX_COL_LINK = 6; public DynamicTableModel() { try { linkURL = new URL("http://www.sun.com"); } catch (MalformedURLException ex) { throw new RuntimeException(ex); } columnSamples = new Object[12]; columnSamples[0] = new Integer(0); columnSamples[1] = "Simple String Value"; columnSamples[2] = new Integer(1000); columnSamples[3] = Boolean.TRUE; columnSamples[4] = new Date(100); columnSamples[5] = new Float(1.5); columnSamples[IDX_COL_LINK] = new LinkModel("Sun Micro", "_blank", linkURL); columnSamples[7] = new Integer(3023); columnSamples[8] = "John Doh"; columnSamples[9] = "23434 Testcase St"; columnSamples[10] = new Integer(33333); columnSamples[11] = Boolean.FALSE; columnSamples2 = new Object[12]; columnSamples2[0] = new Integer(0); columnSamples2[1] = "Another String Value"; columnSamples2[2] = new Integer(999); columnSamples2[3] = Boolean.FALSE; columnSamples2[4] = new Date(333); columnSamples2[5] = new Float(22.22); columnSamples2[IDX_COL_LINK] = new LinkModel("Sun Web", "new_frame", linkURL); columnSamples[7] = new Integer(5503); columnSamples[8] = "Jane Smith"; columnSamples[9] = "2343 Table Blvd."; columnSamples[10] = new Integer(2); columnSamples[11] = Boolean.TRUE; } public DynamicTableModel(Object columnSamples[]) { this.columnSamples = columnSamples; } @Override public Class<?> getColumnClass(int column) { return columnSamples[column].getClass(); } @Override public int getRowCount() { return 1000; } @Override public int getColumnCount() { return columnSamples.length; } @Override public Object getValueAt(int row, int column) { Object value; if (row % 3 == 0) { value = columnSamples[column]; } else { value = columnSamples2[column]; } return column == 0 ? new Integer(row >> 3) : column == 3 ? new Boolean(row % 2 == 0) : value; } @Override public boolean isCellEditable(int row, int column) { return (column == 1); } @Override public void setValueAt(Object aValue, int row, int column) { if (column == 1) { if (row % 3 == 0) { columnSamples[column] = aValue; } else { columnSamples2[column] = aValue; } } this.fireTableDataChanged(); } } // test per-column highlighting private static class TestingHighlighter extends AbstractHighlighter { private List<Highlighter> events; public TestingHighlighter(List<Highlighter> events) { this.events = events; } @Override protected Component doHighlight(Component component, ComponentAdapter adapter) { events.add(this); return component; } } @Test public void testColumnHighlighting() { JXTable table = new JXTable(tableModel); List<Highlighter> events = new ArrayList<Highlighter>(); Highlighter tableHighlighter = new TestingHighlighter(events); Highlighter columnHighlighter = new TestingHighlighter(events); //sanity check assertEquals(0, events.size()); table.addHighlighter(tableHighlighter); table.getColumnExt(0).addHighlighter(columnHighlighter); //explicity prepare the renderer table.prepareRenderer(new DefaultTableCellRenderer(), 0, 0); assertEquals(2, events.size()); assertSame(events.get(0), tableHighlighter); assertSame(events.get(1), columnHighlighter); events.clear(); //explicity prepare the renderer table.prepareRenderer(new DefaultTableCellRenderer(), 0, 1); assertEquals(1, events.size()); assertSame(events.get(0), tableHighlighter); } @Before public void setUpJu4() throws Exception { // just a little conflict between ant and maven builds // junit4 @before methods needs to be public, while // junit3 setUp() inherited from super is protected this.setUp(); } @Override protected void setUp() throws Exception { super.setUp(); // set loader priority to normal if (tableModel == null) { tableModel = new DynamicTableModel(); } sortableTableModel = new AncientSwingTeam(); // make sure we have the same default for each test defaultToSystemLF = true; setSystemLF(defaultToSystemLF); uiTableRowHeight = UIManager.get("JXTable.rowHeight"); sv = createColorStringValue(); table = new JXTable(); } @Override @After public void tearDown() throws Exception { UIManager.put("JXTable.rowHeight", uiTableRowHeight); super.tearDown(); } }