1 /*
   2  * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 import static com.sun.javafx.scene.control.infrastructure.ControlTestUtils.assertStyleClassContains;
  29 import static javafx.scene.control.TableColumn.SortType.ASCENDING;
  30 import static javafx.scene.control.TableColumn.SortType.DESCENDING;
  31 import static org.junit.Assert.*;
  32 
  33 import java.util.*;
  34 import java.util.concurrent.atomic.AtomicLong;
  35 import java.util.function.Supplier;
  36 
  37 import com.sun.javafx.scene.control.ReadOnlyUnbackedObservableList;
  38 import com.sun.javafx.scene.control.SelectedCellsMap;
  39 import com.sun.javafx.scene.control.behavior.TableCellBehavior;
  40 import com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
  41 import com.sun.javafx.scene.control.infrastructure.KeyModifier;
  42 import com.sun.javafx.scene.control.infrastructure.MouseEventFirer;
  43 import com.sun.javafx.scene.control.infrastructure.StageLoader;
  44 import javafx.application.Platform;
  45 import javafx.beans.binding.Bindings;
  46 import javafx.beans.binding.ObjectBinding;
  47 import javafx.beans.property.*;
  48 import javafx.collections.FXCollections;
  49 import javafx.collections.ListChangeListener;
  50 import javafx.collections.ObservableList;
  51 import javafx.collections.transformation.SortedList;
  52 import javafx.event.EventHandler;
  53 import javafx.geometry.Orientation;
  54 import javafx.scene.Group;
  55 import javafx.scene.Scene;
  56 import javafx.scene.control.cell.*;
  57 import javafx.scene.control.skin.TableCellSkin;
  58 import javafx.scene.control.skin.TableColumnHeader;
  59 import javafx.scene.control.skin.TableColumnHeaderRetriever;
  60 import javafx.scene.control.skin.TableHeaderRow;
  61 import javafx.scene.control.skin.TableHeaderRowRetriever;
  62 import javafx.scene.control.skin.VirtualFlow;
  63 import com.sun.javafx.scene.control.VirtualScrollBar;
  64 import javafx.scene.image.ImageView;
  65 import javafx.scene.input.KeyCode;
  66 import javafx.scene.layout.StackPane;
  67 import javafx.scene.layout.VBox;
  68 import javafx.scene.paint.Color;
  69 import javafx.scene.shape.Rectangle;
  70 import javafx.util.Callback;
  71 
  72 import com.sun.javafx.tk.Toolkit;
  73 import org.junit.Before;
  74 import org.junit.Ignore;
  75 import org.junit.Test;
  76 
  77 import com.sun.javafx.scene.control.TableColumnComparatorBase.TableColumnComparator;
  78 import com.sun.javafx.scene.control.infrastructure.VirtualFlowTestUtils;
  79 import com.sun.javafx.scene.control.test.Person;
  80 import com.sun.javafx.scene.control.test.RT_22463_Person;
  81 
  82 import static javafx.scene.control.skin.TableColumnHeaderRetriever.*;
  83 import static org.junit.Assert.assertEquals;
  84 
  85 public class TableViewTest {
  86     private TableView<String> table;
  87     private TableView.TableViewSelectionModel sm;
  88     private TableView.TableViewFocusModel<String> fm;
  89 
  90     private ObservableList<Person> personTestData;
  91 
  92     @Before public void setup() {
  93         table = new TableView<>();
  94         sm = table.getSelectionModel();
  95         fm = table.getFocusModel();
  96 
  97         personTestData = FXCollections.observableArrayList(
  98                 new Person("Jacob", "Smith", "jacob.smith@example.com"),
  99                 new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
 100                 new Person("Ethan", "Williams", "ethan.williams@example.com"),
 101                 new Person("Emma", "Jones", "emma.jones@example.com"),
 102                 new Person("Michael", "Brown", "michael.brown@example.com"));
 103     }
 104     
 105 
 106     /*********************************************************************
 107      * Tests for the constructors                                        *
 108      ********************************************************************/
 109 
 110     @Test public void noArgConstructorSetsTheStyleClass() {
 111         assertStyleClassContains(table, "table-view");
 112     }
 113 
 114     @Test public void noArgConstructorSetsNonNullSelectionModel() {
 115         assertNotNull(sm);
 116     }
 117 
 118     @Test public void noArgConstructorSetsNonNullItems() {
 119         assertNotNull(table.getItems());
 120     }
 121     
 122     @Test public void noArgConstructorSetsNonNullSortPolicy() {
 123         assertNotNull(table.getSortPolicy());
 124     }
 125     
 126     @Test public void noArgConstructorSetsNullComparator() {
 127         assertNull(table.getComparator());
 128     }
 129     
 130     @Test public void noArgConstructorSetsNullOnSort() {
 131         assertNull(table.getOnSort());
 132     }
 133 
 134     @Test public void noArgConstructor_selectedItemIsNull() {
 135         assertNull(sm.getSelectedItem());
 136     }
 137 
 138     @Test public void noArgConstructor_selectedIndexIsNegativeOne() {
 139         assertEquals(-1, sm.getSelectedIndex());
 140     }
 141 
 142     @Test public void singleArgConstructorSetsTheStyleClass() {
 143         final TableView<String> b2 = new TableView<>(FXCollections.observableArrayList("Hi"));
 144         assertStyleClassContains(b2, "table-view");
 145     }
 146 
 147     @Test public void singleArgConstructorSetsNonNullSelectionModel() {
 148         final TableView<String> b2 = new TableView<>(FXCollections.observableArrayList("Hi"));
 149         assertNotNull(b2.getSelectionModel());
 150     }
 151 
 152     @Test public void singleArgConstructorAllowsNullItems() {
 153         final TableView<String> b2 = new TableView<>(null);
 154         assertNull(b2.getItems());
 155     }
 156 
 157     @Test public void singleArgConstructorTakesItems() {
 158         ObservableList<String> items = FXCollections.observableArrayList("Hi");
 159         final TableView<String> b2 = new TableView<>(items);
 160         assertSame(items, b2.getItems());
 161     }
 162 
 163     @Test public void singleArgConstructor_selectedItemIsNull() {
 164         final TableView<String> b2 = new TableView<>(FXCollections.observableArrayList("Hi"));
 165         assertNull(b2.getSelectionModel().getSelectedItem());
 166     }
 167 
 168     @Test public void singleArgConstructor_selectedIndexIsNegativeOne() {
 169         final TableView<String> b2 = new TableView<>(FXCollections.observableArrayList("Hi"));
 170         assertEquals(-1, b2.getSelectionModel().getSelectedIndex());
 171     }
 172 
 173     /*********************************************************************
 174      * Tests for selection model                                         *
 175      ********************************************************************/
 176 
 177     @Test public void selectionModelCanBeNull() {
 178         table.setSelectionModel(null);
 179         assertNull(table.getSelectionModel());
 180     }
 181 
 182     @Test public void selectionModelCanBeBound() {
 183         TableView.TableViewSelectionModel<String> sm = new TableView.TableViewArrayListSelectionModel<String>(table);
 184         ObjectProperty<TableView.TableViewSelectionModel<String>> other = new SimpleObjectProperty<TableView.TableViewSelectionModel<String>>(sm);
 185         table.selectionModelProperty().bind(other);
 186         assertSame(sm, sm);
 187     }
 188 
 189     @Test public void selectionModelCanBeChanged() {
 190         TableView.TableViewSelectionModel<String> sm = new TableView.TableViewArrayListSelectionModel<String>(table);
 191         table.setSelectionModel(sm);
 192         assertSame(sm, sm);
 193     }
 194 
 195     @Test public void canSetSelectedItemToAnItemEvenWhenThereAreNoItems() {
 196         final String randomString = new String("I AM A CRAZY RANDOM STRING");
 197         sm.select(randomString);
 198         assertEquals(-1, sm.getSelectedIndex());
 199         assertSame(randomString, sm.getSelectedItem());
 200     }
 201 
 202     @Test public void canSetSelectedItemToAnItemNotInTheDataModel() {
 203         table.getItems().addAll("Apple", "Orange", "Banana");
 204         final String randomString = new String("I AM A CRAZY RANDOM STRING");
 205         sm.select(randomString);
 206         assertSame(randomString, sm.getSelectedItem());
 207         assertEquals(-1, sm.getSelectedIndex());
 208     }
 209 
 210     @Test public void settingTheSelectedItemToAnItemInItemsResultsInTheCorrectSelectedIndex() {
 211         table.getItems().addAll("Apple", "Orange", "Banana");
 212         sm.select("Orange");
 213         assertEquals(1, sm.getSelectedIndex());
 214         assertSame("Orange", sm.getSelectedItem());
 215     }
 216 
 217     @Test public void settingTheSelectedItemToANonexistantItemAndThenSettingItemsWhichContainsItResultsInCorrectSelectedIndex() {
 218         sm.select("Orange");
 219         table.getItems().addAll("Apple", "Orange", "Banana");
 220         assertEquals(1, sm.getSelectedIndex());
 221         assertSame("Orange", sm.getSelectedItem());
 222     }
 223     
 224     @Test public void ensureSelectionClearsWhenAllItemsAreRemoved_selectIndex0() {
 225         table.getItems().addAll("Apple", "Orange", "Banana");
 226         sm.select(0);
 227         table.getItems().clear();
 228         assertEquals(-1, sm.getSelectedIndex());
 229     }
 230     
 231     @Test public void ensureSelectionClearsWhenAllItemsAreRemoved_selectIndex2() {
 232         table.getItems().addAll("Apple", "Orange", "Banana");
 233         sm.select(2);
 234         table.getItems().clear();
 235         assertEquals(-1, sm.getSelectedIndex());
 236     }
 237     
 238     @Test public void ensureSelectedItemRemainsAccurateWhenItemsAreCleared() {
 239         table.getItems().addAll("Apple", "Orange", "Banana");
 240         sm.select(2);
 241         table.getItems().clear();
 242         assertNull("Selected Item: " + sm.getSelectedItem(), sm.getSelectedItem());
 243         assertEquals(-1, sm.getSelectedIndex());
 244         
 245         table.getItems().addAll("Kiwifruit", "Mandarin", "Pineapple");
 246         sm.select(2);
 247         assertEquals("Pineapple", sm.getSelectedItem());
 248     }
 249     
 250     @Ignore("Not fixed yet")
 251     @Test public void ensureSelectionShiftsDownWhenOneNewItemIsAdded() {
 252         table.getItems().addAll("Apple", "Orange", "Banana");
 253         sm.select(1);
 254         assertEquals(1, sm.getSelectedIndex());
 255         assertEquals("Orange", sm.getSelectedItem());
 256         
 257         table.getItems().add(0, "Kiwifruit");
 258         assertEquals(2, sm.getSelectedIndex());
 259         assertEquals("Orange", sm.getSelectedItem());
 260     }
 261     
 262     @Ignore("Not fixed yet")
 263     @Test public void ensureSelectionShiftsDownWhenMultipleNewItemAreAdded() {
 264         table.getItems().addAll("Apple", "Orange", "Banana");
 265         sm.select(1);
 266         assertEquals(1, sm.getSelectedIndex());
 267         assertEquals("Orange", sm.getSelectedItem());
 268         
 269         table.getItems().addAll(0, Arrays.asList("Kiwifruit", "Pineapple", "Mandarin"));
 270         assertEquals("Orange", sm.getSelectedItem());
 271         assertEquals(4, sm.getSelectedIndex());
 272     }
 273     
 274     @Ignore("Not fixed yet")
 275     @Test public void ensureSelectionShiftsDownWhenOneItemIsRemoved() {
 276         table.getItems().addAll("Apple", "Orange", "Banana");
 277         sm.select(1);
 278         assertEquals(1, sm.getSelectedIndex());
 279         assertEquals("Orange", sm.getSelectedItem());
 280         
 281         table.getItems().remove("Apple");
 282         assertEquals(0, sm.getSelectedIndex());
 283         assertEquals("Orange", sm.getSelectedItem());
 284     }
 285     
 286     @Ignore("Not fixed yet")
 287     @Test public void ensureSelectionShiftsDownWheMultipleItemsAreRemoved() {
 288         table.getItems().addAll("Apple", "Orange", "Banana");
 289         sm.select(2);
 290         assertEquals(2, sm.getSelectedIndex());
 291         assertEquals("Banana", sm.getSelectedItem());
 292         
 293         table.getItems().removeAll(Arrays.asList("Apple", "Orange"));
 294         assertEquals(0, sm.getSelectedIndex());
 295         assertEquals("Banana", sm.getSelectedItem());
 296     }
 297     
 298     @Test public void ensureSelectionIsCorrectWhenItemsChange() {
 299         table.setItems(FXCollections.observableArrayList("Item 1"));
 300         sm.select(0);
 301         assertEquals("Item 1", sm.getSelectedItem());
 302         
 303         table.setItems(FXCollections.observableArrayList("Item 2"));
 304         assertEquals(-1, sm.getSelectedIndex());
 305         assertNull(sm.getSelectedItem());
 306         assertEquals(0, fm.getFocusedIndex());
 307         assertEquals("Item 2", fm.getFocusedItem());
 308     }
 309 
 310     /*********************************************************************
 311      * Tests for columns                                                 *
 312      ********************************************************************/
 313 
 314     @Test public void testColumns() {
 315         TableColumn col1 = new TableColumn();
 316 
 317         assertNotNull(table.getColumns());
 318         assertEquals(0, table.getColumns().size());
 319 
 320         table.getColumns().add(col1);
 321         assertEquals(1, table.getColumns().size());
 322 
 323         table.getColumns().remove(col1);
 324         assertEquals(0, table.getColumns().size());
 325     }
 326 
 327     @Test public void testVisibleLeafColumns() {
 328         TableColumn col1 = new TableColumn();
 329 
 330         assertNotNull(table.getColumns());
 331         assertEquals(0, table.getColumns().size());
 332 
 333         table.getColumns().add(col1);
 334         assertEquals(1, table.getVisibleLeafColumns().size());
 335 
 336         table.getColumns().remove(col1);
 337         assertEquals(0, table.getVisibleLeafColumns().size());
 338     }
 339     
 340     @Test public void testSortOrderCleanup() {
 341 //        ObservableList<ObservablePerson> persons = ObservablePerson.createFXPersonList();
 342         TableView table = new TableView();
 343         TableColumn<String,String> first = new TableColumn<String,String>("first");
 344         first.setCellValueFactory(new PropertyValueFactory("firstName"));
 345         TableColumn<String,String> second = new TableColumn<String,String>("second");
 346         second.setCellValueFactory(new PropertyValueFactory("lastName"));
 347         table.getColumns().addAll(first, second);
 348         table.getSortOrder().setAll(first, second);
 349         table.getColumns().remove(first);
 350         assertFalse(table.getSortOrder().contains(first));
 351     } 
 352     
 353     
 354     /*********************************************************************
 355      * Tests for new sorting API in JavaFX 8.0                           *
 356      ********************************************************************/
 357     
 358     // TODO test for sort policies returning null
 359     // TODO test for changing column sortType out of order
 360     // TODO test comparator returns to original when sort fails / is consumed
 361     
 362     private static final Callback<TableView<String>, Boolean> NO_SORT_FAILED_SORT_POLICY =
 363             tableView -> false;
 364     
 365     private static final Callback<TableView<String>, Boolean> SORT_SUCCESS_ASCENDING_SORT_POLICY =
 366             tableView -> {
 367                 if (tableView.getSortOrder().isEmpty()) return true;
 368                 FXCollections.sort(tableView.getItems(), new Comparator<String>() {
 369                     @Override public int compare(String o1, String o2) {
 370                         return o1.compareTo(o2);
 371                     }
 372                 });
 373                 return true;
 374             };
 375     
 376     private TableColumn<String, String> initSortTestStructure() {
 377         TableColumn<String, String> col = new TableColumn<String, String>("column");
 378         col.setSortType(ASCENDING);
 379         col.setCellValueFactory(param -> new ReadOnlyObjectWrapper<String>(param.getValue()));
 380         table.getColumns().add(col);
 381         table.getItems().addAll("Apple", "Orange", "Banana");
 382         return col;
 383     }
 384     
 385     @Ignore("This test is only valid if sort event consumption should revert changes")
 386     @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeSortOrderList() {
 387         TableColumn<String, String> col = initSortTestStructure();
 388         table.setOnSort(event -> {
 389             event.consume();
 390         });
 391         
 392         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 393         table.getSortOrder().add(col);
 394         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 395         
 396         // the sort order list should be returned back to its original state
 397         assertTrue(table.getSortOrder().isEmpty());
 398     }
 399     
 400     @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeSortOrderList() {
 401         TableColumn<String, String> col = initSortTestStructure();
 402         table.setOnSort(event -> {
 403             // do not consume here - this allows the sort to happen
 404         });
 405         
 406         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 407         table.getSortOrder().add(col);
 408         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
 409         
 410         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getSortOrder(), col);
 411     }
 412     
 413     @Ignore("This test is only valid if sort event consumption should revert changes")
 414     @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeColumnSortType_AscendingToDescending() {
 415         TableColumn<String, String> col = initSortTestStructure();
 416         assertEquals(ASCENDING, col.getSortType());
 417         table.getSortOrder().add(col);
 418         table.setOnSort(event -> {
 419             event.consume();
 420         });
 421         
 422         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
 423         
 424         // when we change from ASCENDING to DESCENDING we don't expect the sort
 425         // to actually change (and in fact we expect the sort type to resort
 426         // back to being ASCENDING)
 427         col.setSortType(DESCENDING);
 428         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
 429         assertEquals(ASCENDING, col.getSortType());
 430         
 431         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getSortOrder(), col);
 432     }
 433     
 434     @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeColumnSortType_AscendingToDescending() {
 435         TableColumn<String, String> col = initSortTestStructure();
 436         assertEquals(ASCENDING, col.getSortType());
 437         table.getSortOrder().add(col);
 438         table.setOnSort(event -> {
 439             // do not consume here - this allows the sort to happen
 440         });
 441         
 442         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
 443         
 444         col.setSortType(DESCENDING);
 445         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 446         assertEquals(DESCENDING, col.getSortType());
 447         
 448         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getSortOrder(), col);
 449     }
 450     
 451     @Ignore("This test is only valid if sort event consumption should revert changes")
 452     @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeColumnSortType_DescendingToNull() {
 453         TableColumn<String, String> col = initSortTestStructure();
 454         col.setSortType(DESCENDING);
 455         assertEquals(DESCENDING, col.getSortType());
 456         table.getSortOrder().add(col);
 457         table.setOnSort(event -> {
 458             event.consume();
 459         });
 460         
 461         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 462         
 463         col.setSortType(null);
 464         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 465         assertEquals(DESCENDING, col.getSortType());
 466         
 467         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getSortOrder(), col);
 468     }
 469     
 470     @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeColumnSortType_DescendingToNull() {
 471         TableColumn<String, String> col = initSortTestStructure();
 472         col.setSortType(DESCENDING);
 473         assertEquals(DESCENDING, col.getSortType());
 474         table.getSortOrder().add(col);
 475         table.setOnSort(event -> {
 476             // do not consume here - this allows the sort to happen
 477         });
 478         
 479         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 480         
 481         col.setSortType(null);
 482         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 483         assertNull(col.getSortType());
 484         
 485         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getSortOrder(), col);
 486     }
 487     
 488     @Ignore("This test is only valid if sort event consumption should revert changes")
 489     @Test public void testSortEventCanBeConsumedToStopSortOccurring_changeColumnSortType_NullToAscending() {
 490         TableColumn<String, String> col = initSortTestStructure();
 491         col.setSortType(null);
 492         assertNull(col.getSortType());
 493         table.getSortOrder().add(col);
 494         table.setOnSort(event -> {
 495             event.consume();
 496         });
 497         
 498         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 499         
 500         col.setSortType(ASCENDING);
 501         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 502         assertNull(col.getSortType());
 503         
 504         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getSortOrder(), col);
 505     }
 506     
 507     @Test public void testSortEventCanBeNotConsumedToAllowSortToOccur_changeColumnSortType_NullToAscending() {
 508         TableColumn<String, String> col = initSortTestStructure();
 509         col.setSortType(null);
 510         assertNull(col.getSortType());
 511         table.getSortOrder().add(col);
 512         table.setOnSort(event -> {
 513             // do not consume here - this allows the sort to happen
 514         });
 515         
 516         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 517         
 518         col.setSortType(ASCENDING);
 519         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
 520         assertEquals(ASCENDING, col.getSortType());
 521         
 522         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getSortOrder(), col);
 523     }
 524 
 525     @Test public void testSortMethodWithNullSortPolicy() {
 526         TableColumn<String, String> col = initSortTestStructure();
 527         table.setSortPolicy(null);
 528         assertNull(table.getSortPolicy());
 529         table.sort();
 530     }
 531     
 532     @Test public void testChangingSortPolicyUpdatesItemsList() {
 533         TableColumn<String, String> col = initSortTestStructure();
 534         col.setSortType(DESCENDING);
 535         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 536         table.getSortOrder().add(col);
 537         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 538         table.setSortPolicy(SORT_SUCCESS_ASCENDING_SORT_POLICY);
 539         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
 540     }
 541     
 542     @Test public void testChangingSortPolicyDoesNotUpdateItemsListWhenTheSortOrderListIsEmpty() {
 543         TableColumn<String, String> col = initSortTestStructure();
 544         col.setSortType(DESCENDING);
 545         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 546         
 547         table.setSortPolicy(SORT_SUCCESS_ASCENDING_SORT_POLICY);
 548         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 549     }
 550     
 551     @Test public void testFailedSortPolicyBacksOutLastChange_sortOrderAddition() {
 552         TableColumn<String, String> col = initSortTestStructure();
 553         col.setSortType(DESCENDING);
 554         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 555         table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
 556         
 557         table.getSortOrder().add(col);
 558         
 559         // no sort should be run (as we have a custom sort policy), and the 
 560         // sortOrder list should be empty as the sortPolicy failed
 561         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 562         assertTrue(table.getSortOrder().isEmpty());
 563     }
 564     
 565     @Test public void testFailedSortPolicyBacksOutLastChange_sortOrderRemoval() {
 566         TableColumn<String, String> col = initSortTestStructure();
 567         col.setSortType(DESCENDING);
 568         table.getSortOrder().add(col);
 569         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 570         
 571         table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
 572 
 573         // even though we remove the column from the sort order here, because the
 574         // sort policy fails the items list should remain unchanged and the sort
 575         // order list should continue to have the column in it.
 576         table.getSortOrder().remove(col);
 577         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 578         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getSortOrder(), col);
 579     }
 580     
 581     @Test public void testFailedSortPolicyBacksOutLastChange_sortTypeChange_ascendingToDescending() {
 582         TableColumn<String, String> col = initSortTestStructure();
 583         col.setSortType(ASCENDING);
 584         table.getSortOrder().add(col);
 585         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
 586         
 587         table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
 588 
 589         col.setSortType(DESCENDING);
 590         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Banana", "Orange");
 591         assertEquals(ASCENDING, col.getSortType());
 592     }
 593     
 594     @Test public void testFailedSortPolicyBacksOutLastChange_sortTypeChange_descendingToNull() {
 595         TableColumn<String, String> col = initSortTestStructure();
 596         col.setSortType(DESCENDING);
 597         table.getSortOrder().add(col);
 598         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 599         
 600         table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
 601 
 602         col.setSortType(null);
 603         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 604         assertEquals(DESCENDING, col.getSortType());
 605     }
 606     
 607     @Test public void testFailedSortPolicyBacksOutLastChange_sortTypeChange_nullToAscending() {
 608         TableColumn<String, String> col = initSortTestStructure();
 609         col.setSortType(null);
 610         table.getSortOrder().add(col);
 611         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 612         
 613         table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
 614 
 615         col.setSortType(ASCENDING);
 616         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 617         assertNull(col.getSortType());
 618     }
 619     
 620     @Test public void testComparatorChangesInSyncWithSortOrder_1() {
 621         TableColumn<String, String> col = initSortTestStructure();
 622         assertNull(table.getComparator());
 623         assertTrue(table.getSortOrder().isEmpty());
 624         
 625         table.getSortOrder().add(col);
 626         TableColumnComparator c = (TableColumnComparator)table.getComparator();
 627         assertNotNull(c);
 628         VirtualFlowTestUtils.assertListContainsItemsInOrder(c.getColumns(), col);
 629     }
 630     
 631     @Ignore
 632     @Test public void testComparatorChangesInSyncWithSortOrder_2() {
 633         // same as test above
 634         TableColumn<String, String> col = initSortTestStructure();
 635         assertNull(table.getComparator());
 636         assertTrue(table.getSortOrder().isEmpty());
 637         
 638         table.getSortOrder().add(col);
 639         TableColumnComparator c = (TableColumnComparator)table.getComparator();
 640         assertNotNull(c);
 641         VirtualFlowTestUtils.assertListContainsItemsInOrder(c.getColumns(), col);
 642         
 643         // now remove column from sort order, and the comparator should go to
 644         // being null
 645         table.getSortOrder().remove(col);
 646         assertNull(table.getComparator());
 647     }
 648     
 649     @Test public void testFailedSortPolicyBacksOutComparatorChange_sortOrderAddition() {
 650         TableColumn<String, String> col = initSortTestStructure();
 651         final TableColumnComparator oldComparator = (TableColumnComparator)table.getComparator();
 652         
 653         col.setSortType(DESCENDING);
 654         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Apple", "Orange", "Banana");
 655         table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
 656         
 657         table.getSortOrder().add(col);
 658         
 659         assertEquals(oldComparator, table.getComparator());
 660     }
 661     
 662     @Test public void testFailedSortPolicyBacksOutComparatorChange_sortOrderRemoval() {
 663         TableColumn<String, String> col = initSortTestStructure();
 664         TableColumnComparator oldComparator = (TableColumnComparator)table.getComparator();
 665         assertNull(oldComparator);
 666 
 667         col.setSortType(DESCENDING);
 668         table.getSortOrder().add(col);
 669         VirtualFlowTestUtils.assertListContainsItemsInOrder(table.getItems(), "Orange", "Banana", "Apple");
 670         oldComparator = (TableColumnComparator)table.getComparator();
 671         VirtualFlowTestUtils.assertListContainsItemsInOrder(oldComparator.getColumns(), col);
 672         
 673         table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
 674         table.getSortOrder().remove(col);
 675         
 676         assertTrue(table.getSortOrder().contains(col));
 677         VirtualFlowTestUtils.assertListContainsItemsInOrder(oldComparator.getColumns(), col);
 678     }
 679     
 680     @Test public void testFailedSortPolicyBacksOutComparatorChange_sortTypeChange() {
 681         TableColumn<String, String> col = initSortTestStructure();
 682         final TableColumnComparator oldComparator = (TableColumnComparator)table.getComparator();
 683         assertNull(oldComparator);
 684         
 685         table.setSortPolicy(NO_SORT_FAILED_SORT_POLICY);
 686         table.getSortOrder().add(col);
 687         col.setSortType(ASCENDING);
 688         
 689         assertTrue(table.getSortOrder().isEmpty());
 690         assertNull(oldComparator);
 691     }
 692 
 693     @Test public void testComparatorIsNullWhenSortOrderListIsEmpty() {
 694         TableColumn<String, String> col = initSortTestStructure();
 695 
 696         assertNull(table.getComparator());
 697 
 698         table.getSortOrder().add(col);
 699         assertFalse(table.getSortOrder().isEmpty());
 700         assertNotNull(table.getComparator());
 701 
 702         table.getSortOrder().clear();
 703         assertTrue(table.getSortOrder().isEmpty());
 704         assertNull(table.getComparator());
 705     }
 706     
 707     
 708     
 709     /*********************************************************************
 710      * Tests for specific bugs                                           *
 711      ********************************************************************/
 712     @Test public void test_rt16019() {
 713         // RT-16019: NodeMemory TableView tests fail with 
 714         // IndexOutOfBoundsException (ObservableListWrapper.java:336)
 715         TableView table = new TableView();
 716         for (int i = 0; i < 1000; i++) {
 717             table.getItems().add("data " + i);
 718         }
 719     }
 720     
 721     @Test public void test_rt15793() {
 722         // ListView/TableView selectedIndex is 0 although the items list is empty
 723         final TableView tv = new TableView();
 724         final ObservableList list = FXCollections.observableArrayList();
 725         tv.setItems(list);
 726         list.add("toto");
 727         tv.getSelectionModel().select(0);
 728         assertEquals(0, tv.getSelectionModel().getSelectedIndex());
 729         list.remove(0);
 730         assertEquals(-1, tv.getSelectionModel().getSelectedIndex());
 731     }
 732 
 733     @Ignore("Started failing recently, but manual tests seem to indicate the functionality is intact")
 734     @Test public void test_rt17522_focusShouldMoveWhenItemAddedAtFocusIndex() {
 735         final TableView lv = new TableView();
 736         StageLoader sl = new StageLoader(lv);
 737         FocusModel fm = lv.getFocusModel();
 738         lv.getItems().add("row1");
 739         assertTrue(fm.isFocused(0));
 740         
 741         lv.getItems().add(0, "row0");
 742         assertTrue("Focus is on " + fm.getFocusedIndex(), fm.isFocused(1));
 743         assertFalse(fm.isFocused(0));
 744 
 745         sl.dispose();
 746     }
 747     
 748     @Test public void test_rt17522_focusShouldMoveWhenItemAddedBeforeFocusIndex() {
 749         final TableView lv = new TableView();
 750         FocusModel fm = lv.getFocusModel();
 751         lv.getItems().addAll("row1", "row2");
 752         fm.focus(1);
 753         assertTrue(fm.isFocused(1));
 754         assertEquals("row2", fm.getFocusedItem());
 755         
 756         lv.getItems().add(1, "row0");
 757         assertTrue(fm.isFocused(2));
 758         assertEquals("row2", fm.getFocusedItem());
 759         assertFalse(fm.isFocused(1));
 760     }
 761     
 762     @Test public void test_rt17522_focusShouldNotMoveWhenItemAddedAfterFocusIndex() {
 763         final TableView lv = new TableView();
 764         FocusModel fm = lv.getFocusModel();
 765         lv.getItems().addAll("row1");
 766         fm.focus(0);
 767         assertTrue(fm.isFocused(0));
 768         assertEquals("row1", fm.getFocusedItem());
 769         
 770         lv.getItems().add(1, "row2");
 771         assertTrue(fm.isFocused(0));
 772         assertEquals("row1", fm.getFocusedItem());
 773         assertFalse(fm.isFocused(1));
 774     }
 775     
 776     @Test public void test_rt17522_focusShouldBeResetWhenFocusedItemIsRemoved() {
 777         final TableView lv = new TableView();
 778         FocusModel fm = lv.getFocusModel();
 779         lv.getItems().add("row1");
 780         assertTrue(fm.isFocused(0));
 781 
 782         lv.getItems().remove("row1");
 783         assertTrue(fm.getFocusedIndex() == -1);
 784         assertNull(fm.getFocusedItem());
 785     }
 786     
 787     @Test public void test_rt17522_focusShouldMoveWhenItemRemovedBeforeFocusIndex() {
 788         final TableView lv = new TableView();
 789         FocusModel fm = lv.getFocusModel();
 790         lv.getItems().addAll("row1", "row2");
 791         assertTrue(fm.isFocused(0));
 792 
 793         fm.focus(1);
 794         assertTrue(fm.isFocused(1));
 795         assertEquals("row2", fm.getFocusedItem());
 796         
 797         lv.getItems().remove("row1");
 798         assertTrue(fm.isFocused(0));
 799         assertEquals("row2", fm.getFocusedItem());
 800     }
 801     
 802     @Test public void test_rt17522_focusShouldNotMoveWhenItemRemovedAfterFocusIndex() {
 803         final TableView lv = new TableView();
 804         FocusModel fm = lv.getFocusModel();
 805         lv.getItems().addAll("row1", "row2");
 806         fm.focus(0);
 807         assertTrue(fm.isFocused(0));
 808         assertEquals("row1", fm.getFocusedItem());
 809         
 810         lv.getItems().remove("row2");
 811         assertTrue(fm.isFocused(0));
 812         assertEquals("row1", fm.getFocusedItem());
 813     }
 814     
 815     @Test public void test_rt18385() {
 816         table.getItems().addAll("row1", "row2", "row3");
 817         sm.select(1);
 818         table.getItems().add("Another Row");
 819         assertEquals(1, sm.getSelectedIndices().size());
 820         assertEquals(1, sm.getSelectedItems().size());
 821         assertEquals(1, sm.getSelectedCells().size());
 822     }
 823     
 824     @Test public void test_rt18339_onlyEditWhenTableViewIsEditable_tableEditableIsFalse_columnEditableIsFalse() {
 825         TableColumn<String,String> first = new TableColumn<String,String>("first");
 826         first.setEditable(false);
 827         table.getColumns().add(first);
 828         table.setEditable(false);
 829         table.edit(1, first);
 830         assertEquals(null, table.getEditingCell());
 831     }
 832     
 833     @Test public void test_rt18339_onlyEditWhenTableViewIsEditable_tableEditableIsFalse_columnEditableIsTrue() {
 834         TableColumn<String,String> first = new TableColumn<String,String>("first");
 835         first.setEditable(true);
 836         table.getColumns().add(first);
 837         table.setEditable(false);
 838         table.edit(1, first);
 839         assertEquals(null, table.getEditingCell());
 840     }
 841     
 842     @Test public void test_rt18339_onlyEditWhenTableViewIsEditable_tableEditableIsTrue_columnEditableIsFalse() {
 843         TableColumn<String,String> first = new TableColumn<String,String>("first");
 844         first.setEditable(false);
 845         table.getColumns().add(first);
 846         table.setEditable(true);
 847         table.edit(1, first);
 848         assertEquals(null, table.getEditingCell());
 849     }
 850     
 851     @Test public void test_rt18339_onlyEditWhenTableViewIsEditable_tableEditableIsTrue_columnEditableIsTrue() {
 852         TableColumn<String,String> first = new TableColumn<String,String>("first");
 853         first.setEditable(true);
 854         table.getColumns().add(first);
 855         table.setEditable(true);
 856         table.edit(1, first);
 857         assertEquals(new TablePosition(table, 1, first), table.getEditingCell());
 858     }
 859     
 860     @Test public void test_rt14451() {
 861         table.getItems().addAll("Apple", "Orange", "Banana");
 862         sm.setSelectionMode(SelectionMode.MULTIPLE);
 863         sm.selectRange(0, 2); // select from 0 (inclusive) to 2 (exclusive)
 864         assertEquals(2, sm.getSelectedIndices().size());
 865     }
 866     
 867     @Test public void test_rt21586() {
 868         table.getItems().setAll("Apple", "Orange", "Banana");
 869         table.getSelectionModel().select(1);
 870         assertEquals(1, table.getSelectionModel().getSelectedIndex());
 871         assertEquals("Orange", table.getSelectionModel().getSelectedItem());
 872         
 873         table.getItems().setAll("Kiwifruit", "Pineapple", "Grape");
 874         assertEquals(-1, table.getSelectionModel().getSelectedIndex());
 875         assertNull(table.getSelectionModel().getSelectedItem());
 876         assertEquals(0, table.getFocusModel().getFocusedIndex());
 877         assertEquals("Kiwifruit", table.getFocusModel().getFocusedItem());
 878     }
 879     
 880     @Test public void test_rt27820_1() {
 881         table.getItems().setAll("Apple", "Orange");
 882         table.getSelectionModel().select(0);
 883         assertEquals(1, table.getSelectionModel().getSelectedItems().size());
 884         assertEquals("Apple", table.getSelectionModel().getSelectedItem());
 885         
 886         table.getItems().clear();
 887         assertEquals(0, table.getSelectionModel().getSelectedItems().size());
 888         assertNull(table.getSelectionModel().getSelectedItem());
 889     }
 890     
 891     @Test public void test_rt27820_2() {
 892         table.getItems().setAll("Apple", "Orange");
 893         table.getSelectionModel().select(1);
 894         assertEquals(1, table.getSelectionModel().getSelectedItems().size());
 895         assertEquals("Orange", table.getSelectionModel().getSelectedItem());
 896         
 897         table.getItems().clear();
 898         assertEquals(0, table.getSelectionModel().getSelectedItems().size());
 899         assertNull(table.getSelectionModel().getSelectedItem());
 900     }
 901     
 902     @Test public void test_rt28534() {
 903         TableView<Person> table = new TableView<Person>();
 904         table.setItems(personTestData);
 905         
 906         TableColumn firstNameCol = new TableColumn("First Name");
 907         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
 908 
 909         TableColumn lastNameCol = new TableColumn("Last Name");
 910         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
 911 
 912         TableColumn emailCol = new TableColumn("Email");
 913         emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
 914 
 915         table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
 916         
 917         VirtualFlowTestUtils.assertRowsNotEmpty(table, 0, 5); // rows 0 - 5 should be filled
 918         VirtualFlowTestUtils.assertRowsEmpty(table, 5, -1); // rows 5+ should be empty
 919         
 920         // now we replace the data and expect the cells that have no data
 921         // to be empty
 922         table.setItems(FXCollections.observableArrayList(
 923             new Person("*_*Emma", "Jones", "emma.jones@example.com"),
 924             new Person("_Michael", "Brown", "michael.brown@example.com")));
 925         
 926         VirtualFlowTestUtils.assertRowsNotEmpty(table, 0, 2); // rows 0 - 2 should be filled
 927         VirtualFlowTestUtils.assertRowsEmpty(table, 2, -1); // rows 2+ should be empty
 928     }
 929     
 930     @Test public void test_rt22463() {
 931         final TableView<RT_22463_Person> table = new TableView<RT_22463_Person>();
 932         table.setTableMenuButtonVisible(true);
 933         TableColumn c1 = new TableColumn("Id");
 934         TableColumn c2 = new TableColumn("Name");
 935         c1.setCellValueFactory(new PropertyValueFactory<Person, Long>("id"));
 936         c2.setCellValueFactory(new PropertyValueFactory<Person, String>("name"));
 937         table.getColumns().addAll(c1, c2);
 938         
 939         // before the change things display fine
 940         RT_22463_Person p1 = new RT_22463_Person();
 941         p1.setId(1l);
 942         p1.setName("name1");
 943         RT_22463_Person p2 = new RT_22463_Person();
 944         p2.setId(2l);
 945         p2.setName("name2");
 946         table.setItems(FXCollections.observableArrayList(p1, p2));
 947         VirtualFlowTestUtils.assertCellTextEquals(table, 0, "1", "name1");
 948         VirtualFlowTestUtils.assertCellTextEquals(table, 1, "2", "name2");
 949         
 950         // now we change the persons but they are still equal as the ID's don't
 951         // change - but the items list is cleared so the cells should update
 952         RT_22463_Person new_p1 = new RT_22463_Person();
 953         new_p1.setId(1l);
 954         new_p1.setName("updated name1");
 955         RT_22463_Person new_p2 = new RT_22463_Person();
 956         new_p2.setId(2l);
 957         new_p2.setName("updated name2");
 958         table.getItems().clear();
 959         table.setItems(FXCollections.observableArrayList(new_p1, new_p2));
 960         VirtualFlowTestUtils.assertCellTextEquals(table, 0, "1", "updated name1");
 961         VirtualFlowTestUtils.assertCellTextEquals(table, 1, "2", "updated name2");
 962     }
 963     
 964     @Test public void test_rt28637() {
 965         ObservableList<String> items = FXCollections.observableArrayList("String1", "String2", "String3", "String4");
 966         
 967         final TableView<String> tableView = new TableView<String>();
 968         final MultipleSelectionModel sm = tableView.getSelectionModel();
 969         tableView.setItems(items);
 970         
 971         tableView.getSelectionModel().select(0);
 972         assertEquals("String1", sm.getSelectedItem());
 973         assertEquals(1, sm.getSelectedItems().size());
 974         assertEquals("String1", sm.getSelectedItems().get(0));
 975         assertEquals(0, sm.getSelectedIndex());
 976         
 977         items.remove(sm.getSelectedItem());
 978         assertEquals("String2", sm.getSelectedItem());
 979         assertEquals(1, sm.getSelectedItems().size());
 980         assertEquals("String2", sm.getSelectedItems().get(0));
 981         assertEquals(0, sm.getSelectedIndex());
 982     }
 983     
 984     @Test public void test_rt24844() {
 985         // p1 == lowest first name
 986         Person p0, p1, p2, p3, p4;
 987         
 988         TableView<Person> table = new TableView<Person>();
 989         table.setItems(FXCollections.observableArrayList(
 990             p3 = new Person("Jacob", "Smith", "jacob.smith@example.com"),
 991             p2 = new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
 992             p1 = new Person("Ethan", "Williams", "ethan.williams@example.com"),
 993             p0 = new Person("Emma", "Jones", "emma.jones@example.com"),
 994             p4 = new Person("Michael", "Brown", "michael.brown@example.com")));
 995         
 996         TableColumn firstNameCol = new TableColumn("First Name");
 997         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
 998         
 999         // set dummy comparator to lock items in place until new comparator is set
1000         firstNameCol.setComparator((t, t1) -> 0);
1001 
1002         table.getColumns().addAll(firstNameCol);
1003         table.getSortOrder().add(firstNameCol);
1004         
1005         // ensure the existing order is as expected
1006         assertEquals(p3, table.getItems().get(0));
1007         assertEquals(p2, table.getItems().get(1));
1008         assertEquals(p1, table.getItems().get(2));
1009         assertEquals(p0, table.getItems().get(3));
1010         assertEquals(p4, table.getItems().get(4));
1011         
1012         // set a new comparator
1013         firstNameCol.setComparator(new Comparator() {
1014             Random r =  new Random();
1015             @Override public int compare(Object t, Object t1) {
1016                 return t.toString().compareTo(t1.toString());
1017             }
1018         });
1019         
1020         // ensure the new order is as expected
1021         assertEquals(p0, table.getItems().get(0));
1022         assertEquals(p1, table.getItems().get(1));
1023         assertEquals(p2, table.getItems().get(2));
1024         assertEquals(p3, table.getItems().get(3));
1025         assertEquals(p4, table.getItems().get(4));
1026     }
1027     
1028     @Test public void test_rt29331() {
1029         TableView<Person> table = new TableView<Person>();
1030         
1031         TableColumn firstNameCol = new TableColumn("First Name");
1032         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1033 
1034         TableColumn lastNameCol = new TableColumn("Last Name");
1035         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
1036 
1037         TableColumn emailCol = new TableColumn("Email");
1038         emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
1039         
1040         TableColumn parentColumn = new TableColumn<>("Parent");
1041         parentColumn.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
1042         
1043         table.getColumns().addAll(parentColumn);
1044         
1045         // table is setup, now hide the 'last name' column
1046         emailCol.setVisible(false);
1047         assertFalse(emailCol.isVisible());
1048         
1049         // reorder columns inside the parent column
1050         parentColumn.getColumns().setAll(emailCol, firstNameCol, lastNameCol);
1051         
1052         // the email column should not become visible after this, but it does
1053         assertFalse(emailCol.isVisible());
1054     }
1055     
1056     private int rt29330_count = 0;
1057     @Test public void test_rt29330_1() {
1058         TableView<Person> table = new TableView<>(personTestData);
1059 
1060         TableColumn parentColumn = new TableColumn<>("Parent");
1061         table.getColumns().addAll(parentColumn);
1062         
1063         TableColumn firstNameCol = new TableColumn("First Name");
1064         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1065 
1066         TableColumn lastNameCol = new TableColumn("Last Name");
1067         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
1068         
1069         parentColumn.getColumns().addAll(firstNameCol, lastNameCol);
1070 
1071         table.setOnSort(event -> {
1072             rt29330_count++;
1073         });
1074         
1075         // test preconditions
1076         assertEquals(ASCENDING, lastNameCol.getSortType());
1077         assertEquals(0, rt29330_count);
1078         
1079         table.getSortOrder().add(lastNameCol);
1080         assertEquals(1, rt29330_count);
1081         
1082         lastNameCol.setSortType(DESCENDING);
1083         assertEquals(2, rt29330_count);
1084         
1085         lastNameCol.setSortType(null);
1086         assertEquals(3, rt29330_count);
1087         
1088         lastNameCol.setSortType(ASCENDING);
1089         assertEquals(4, rt29330_count);
1090     }
1091     
1092     @Test public void test_rt29330_2() {
1093         TableView<Person> table = new TableView<>(personTestData);
1094 
1095         TableColumn firstNameCol = new TableColumn("First Name");
1096         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1097 
1098         TableColumn lastNameCol = new TableColumn("Last Name");
1099         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
1100         
1101         // this test differs from the previous one by installing the parent column
1102         // into the tableview after it has the children added into it
1103         TableColumn parentColumn = new TableColumn<>("Parent");
1104         parentColumn.getColumns().addAll(firstNameCol, lastNameCol);
1105         table.getColumns().addAll(parentColumn);
1106 
1107         table.setOnSort(event -> {
1108             rt29330_count++;
1109         });
1110         
1111         // test preconditions
1112         assertEquals(ASCENDING, lastNameCol.getSortType());
1113         assertEquals(0, rt29330_count);
1114         
1115         table.getSortOrder().add(lastNameCol);
1116         assertEquals(1, rt29330_count);
1117         
1118         lastNameCol.setSortType(DESCENDING);
1119         assertEquals(2, rt29330_count);
1120         
1121         lastNameCol.setSortType(null);
1122         assertEquals(3, rt29330_count);
1123         
1124         lastNameCol.setSortType(ASCENDING);
1125         assertEquals(4, rt29330_count);
1126     }
1127     
1128     @Test public void test_rt29313_selectedIndices() {
1129         TableView<Person> table = new TableView<>(personTestData);
1130 
1131         TableSelectionModel sm = table.getSelectionModel();
1132         
1133         TableColumn firstNameCol = new TableColumn("First Name");
1134         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1135 
1136         TableColumn lastNameCol = new TableColumn("Last Name");
1137         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
1138         
1139         TableColumn emailCol = new TableColumn("Email");
1140         emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
1141         
1142         table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
1143         sm.setCellSelectionEnabled(true);
1144         sm.setSelectionMode(SelectionMode.MULTIPLE);
1145 
1146         assertEquals(0, sm.getSelectedIndices().size());
1147 
1148         // only (0,0) should be selected, so selected indices should be [0]
1149         sm.select(0, firstNameCol);
1150         assertEquals(1, sm.getSelectedIndices().size());
1151         
1152         // now (0,0) and (1,0) should be selected, so selected indices should be [0, 1]
1153         sm.select(1, firstNameCol);
1154         assertEquals(2, sm.getSelectedIndices().size());
1155         
1156         // now (0,0), (1,0) and (1,1) should be selected, but selected indices 
1157         // should remain as [0, 1], as we don't want selected indices to become
1158         // [0,1,1] (which is what RT-29313 is about)
1159         sm.select(1, lastNameCol);
1160         assertEquals(2, sm.getSelectedIndices().size());
1161         assertEquals(0, sm.getSelectedIndices().get(0));
1162         assertEquals(1, sm.getSelectedIndices().get(1));
1163     }
1164     
1165     @Test public void test_rt29313_selectedItems() {
1166         Person p0, p1;
1167         TableView<Person> table = new TableView<>();
1168         table.setItems(FXCollections.observableArrayList(
1169               p0 = new Person("Jacob", "Smith", "jacob.smith@example.com"),
1170               p1 = new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
1171               new Person("Ethan", "Williams", "ethan.williams@example.com"),
1172               new Person("Emma", "Jones", "emma.jones@example.com"),
1173               new Person("Michael", "Brown", "michael.brown@example.com")));
1174         
1175         TableSelectionModel sm = table.getSelectionModel();
1176         
1177         TableColumn firstNameCol = new TableColumn("First Name");
1178         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1179 
1180         TableColumn lastNameCol = new TableColumn("Last Name");
1181         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
1182         
1183         TableColumn emailCol = new TableColumn("Email");
1184         emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
1185         
1186         table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
1187         sm.setCellSelectionEnabled(true);
1188         sm.setSelectionMode(SelectionMode.MULTIPLE);
1189 
1190         assertEquals(0, sm.getSelectedIndices().size());
1191 
1192         // only (0,0) should be selected, so selected items should be [p0]
1193         sm.select(0, firstNameCol);
1194         assertEquals(1, sm.getSelectedItems().size());
1195         
1196         // now (0,0) and (1,0) should be selected, so selected items should be [p0, p1]
1197         sm.select(1, firstNameCol);
1198         assertEquals(2, sm.getSelectedItems().size());
1199         
1200         // now (0,0), (1,0) and (1,1) should be selected, but selected items 
1201         // should remain as [p0, p1], as we don't want selected items to become
1202         // [p0,p1,p1] (which is what RT-29313 is about)
1203         sm.select(1, lastNameCol);
1204         assertEquals(2, sm.getSelectedItems().size());
1205         assertEquals(p0, sm.getSelectedItems().get(0));
1206         assertEquals(p1, sm.getSelectedItems().get(1));
1207     }
1208     
1209     @Test public void test_rt29566() {
1210         TableView<Person> table = new TableView<>(personTestData);
1211 
1212         TableSelectionModel sm = table.getSelectionModel();
1213         
1214         TableColumn firstNameCol = new TableColumn("First Name");
1215         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1216 
1217         TableColumn lastNameCol = new TableColumn("Last Name");
1218         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
1219         
1220         TableColumn emailCol = new TableColumn("Email");
1221         emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
1222         
1223         table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
1224         
1225         // test the state before we hide and re-add a column
1226         VirtualFlowTestUtils.assertCellTextEquals(table, 0, "Jacob", "Smith", "jacob.smith@example.com");
1227         VirtualFlowTestUtils.assertCellTextEquals(table, 1, "Isabella", "Johnson", "isabella.johnson@example.com");
1228         VirtualFlowTestUtils.assertCellTextEquals(table, 2, "Ethan", "Williams", "ethan.williams@example.com");
1229         VirtualFlowTestUtils.assertCellTextEquals(table, 3, "Emma", "Jones", "emma.jones@example.com");
1230         VirtualFlowTestUtils.assertCellTextEquals(table, 4, "Michael", "Brown", "michael.brown@example.com");
1231         
1232         // hide the last name column, and test cells again
1233         table.getColumns().remove(lastNameCol);
1234         VirtualFlowTestUtils.assertCellTextEquals(table, 0, "Jacob", "jacob.smith@example.com");
1235         VirtualFlowTestUtils.assertCellTextEquals(table, 1, "Isabella", "isabella.johnson@example.com");
1236         VirtualFlowTestUtils.assertCellTextEquals(table, 2, "Ethan", "ethan.williams@example.com");
1237         VirtualFlowTestUtils.assertCellTextEquals(table, 3, "Emma", "emma.jones@example.com");
1238         VirtualFlowTestUtils.assertCellTextEquals(table, 4, "Michael", "michael.brown@example.com");
1239         
1240         // re-add the last name column - we should go back to the original state.
1241         // However, what appears to be happening is that, for some reason, some
1242         // of the cells from the removed column do not reappear - meaning in this case
1243         // some of the last name values will not be where we expect them to be.
1244         // This is clearly not ideal!
1245         table.getColumns().add(1, lastNameCol);
1246         VirtualFlowTestUtils.assertCellTextEquals(table, 0, "Jacob", "Smith", "jacob.smith@example.com");
1247         VirtualFlowTestUtils.assertCellTextEquals(table, 1, "Isabella", "Johnson", "isabella.johnson@example.com");
1248         VirtualFlowTestUtils.assertCellTextEquals(table, 2, "Ethan", "Williams", "ethan.williams@example.com");
1249         VirtualFlowTestUtils.assertCellTextEquals(table, 3, "Emma", "Jones", "emma.jones@example.com");
1250         VirtualFlowTestUtils.assertCellTextEquals(table, 4, "Michael", "Brown", "michael.brown@example.com");
1251     }
1252     
1253     @Test public void test_rt29390() {
1254         final TableView<Person> tableView = new TableView<Person>();
1255         tableView.setMaxHeight(50);
1256         tableView.setPrefHeight(50);
1257         tableView.setItems(FXCollections.observableArrayList(
1258             new Person("Jacob", "Smith", "jacob.smith@example.com"),
1259             new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
1260             new Person("Ethan", "Williams", "ethan.williams@example.com"),
1261             new Person("Emma", "Jones", "emma.jones@example.com"),
1262             new Person("Jacob", "Smith", "jacob.smith@example.com"),
1263             new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
1264             new Person("Ethan", "Williams", "ethan.williams@example.com"),
1265             new Person("Emma", "Jones", "emma.jones@example.com"),
1266             new Person("Jacob", "Smith", "jacob.smith@example.com"),
1267             new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
1268             new Person("Ethan", "Williams", "ethan.williams@example.com"),
1269             new Person("Emma", "Jones", "emma.jones@example.com"),
1270             new Person("Jacob", "Smith", "jacob.smith@example.com"),
1271             new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
1272             new Person("Ethan", "Williams", "ethan.williams@example.com"),
1273             new Person("Emma", "Jones", "emma.jones@example.com")
1274         ));
1275         
1276         TableColumn firstNameCol = new TableColumn("First Name");
1277         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1278         
1279         tableView.getColumns().add(firstNameCol);
1280         
1281         // we want the vertical scrollbar
1282         VirtualScrollBar scrollBar = VirtualFlowTestUtils.getVirtualFlowVerticalScrollbar(tableView);
1283         
1284         assertNotNull(scrollBar);
1285         assertTrue(scrollBar.isVisible());
1286         assertTrue(scrollBar.getVisibleAmount() > 0.0);
1287         assertTrue(scrollBar.getVisibleAmount() < 1.0);
1288         
1289         // this next test is likely to be brittle, but we'll see...If it is the
1290         // cause of failure then it can be commented out
1291         assertEquals(0.0625, scrollBar.getVisibleAmount(), 0.0);
1292     }
1293 
1294     @Test public void test_rt30400() {
1295         // create a listview that'll render cells using the check box cell factory
1296         final TableView<Person> tableView = new TableView<Person>();
1297         tableView.setMinHeight(100);
1298         tableView.setPrefHeight(100);
1299         tableView.setItems(FXCollections.observableArrayList(
1300                 new Person("Jacob", "Smith", "jacob.smith@example.com")
1301         ));
1302 
1303         TableColumn firstNameCol = new TableColumn("First Name");
1304         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1305         firstNameCol.setCellFactory(CheckBoxTableCell.forTableColumn(param -> new ReadOnlyBooleanWrapper(true)));
1306         tableView.getColumns().add(firstNameCol);
1307 
1308         // because only the first row has data, all other rows should be
1309         // empty (and not contain check boxes - we just check the first four here)
1310         VirtualFlowTestUtils.assertRowsNotEmpty(tableView, 0, 1);
1311         VirtualFlowTestUtils.assertCellNotEmpty(VirtualFlowTestUtils.getCell(tableView, 0));
1312         VirtualFlowTestUtils.assertCellEmpty(VirtualFlowTestUtils.getCell(tableView, 1));
1313         VirtualFlowTestUtils.assertCellEmpty(VirtualFlowTestUtils.getCell(tableView, 2));
1314         VirtualFlowTestUtils.assertCellEmpty(VirtualFlowTestUtils.getCell(tableView, 3));
1315     }
1316 
1317     @Test public void test_rt31165() {
1318         final ObservableList names = FXCollections.observableArrayList("Adam", "Alex", "Alfred", "Albert");
1319 
1320         final TableView<Person> tableView = new TableView<Person>();
1321         tableView.setEditable(true);
1322         tableView.setItems(FXCollections.observableArrayList(
1323             new Person("Jacob", "Smith", "jacob.smith@example.com"),
1324             new Person("Jim", "Bob", "jim.bob@example.com")
1325         ));
1326 
1327         TableColumn firstNameCol = new TableColumn("First Name");
1328         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1329         firstNameCol.setCellFactory(ChoiceBoxTableCell.forTableColumn(names));
1330         firstNameCol.setEditable(true);
1331 
1332         tableView.getColumns().add(firstNameCol);
1333 
1334         IndexedCell cell = VirtualFlowTestUtils.getCell(tableView, 1, 0);
1335         assertEquals("Jim", cell.getText());
1336         assertFalse(cell.isEditing());
1337 
1338         tableView.edit(1, firstNameCol);
1339 
1340         TablePosition editingCell = tableView.getEditingCell();
1341         assertEquals(1, editingCell.getRow());
1342         assertEquals(firstNameCol, editingCell.getTableColumn());
1343         assertTrue(cell.isEditing());
1344 
1345         VirtualFlowTestUtils.getVirtualFlow(tableView).requestLayout();
1346         Toolkit.getToolkit().firePulse();
1347 
1348         editingCell = tableView.getEditingCell();
1349         assertEquals(1, editingCell.getRow());
1350         assertEquals(firstNameCol, editingCell.getTableColumn());
1351         assertTrue(cell.isEditing());
1352     }
1353 
1354     @Test public void test_rt31471() {
1355         Person jacobSmith;
1356         final TableView<Person> tableView = new TableView<Person>();
1357         tableView.setItems(FXCollections.observableArrayList(
1358                 jacobSmith = new Person("Jacob", "Smith", "jacob.smith@example.com"),
1359                 new Person("Jim", "Bob", "jim.bob@example.com")
1360         ));
1361 
1362         TableColumn firstNameCol = new TableColumn("First Name");
1363         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1364         tableView.getColumns().add(firstNameCol);
1365 
1366         IndexedCell cell = VirtualFlowTestUtils.getCell(tableView, 0);
1367         assertEquals(jacobSmith, cell.getItem());
1368 
1369         tableView.setFixedCellSize(50);
1370 
1371         VirtualFlowTestUtils.getVirtualFlow(tableView).requestLayout();
1372         Toolkit.getToolkit().firePulse();
1373 
1374         assertEquals(jacobSmith, cell.getItem());
1375         assertEquals(50, cell.getHeight(), 0.00);
1376     }
1377 
1378     private int rt_31200_count = 0;
1379     @Test public void test_rt_31200_tableCell() {
1380         rt_31200_count = 0;
1381         final TableView<Person> tableView = new TableView<Person>();
1382         tableView.setItems(FXCollections.observableArrayList(
1383                 new Person("Jacob", "Smith", "jacob.smith@example.com"),
1384                 new Person("Jim", "Bob", "jim.bob@example.com")
1385         ));
1386 
1387         TableColumn<Person,String> firstNameCol = new TableColumn("First Name");
1388         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1389         tableView.getColumns().add(firstNameCol);
1390 
1391         firstNameCol.setCellFactory(new Callback<TableColumn<Person,String>, TableCell<Person, String>>() {
1392             @Override
1393             public TableCell<Person, String> call(TableColumn<Person,String> param) {
1394                 return new TableCell<Person, String>() {
1395                     ImageView view = new ImageView();
1396                     { setGraphic(view); };
1397 
1398                     @Override
1399                     protected void updateItem(String item, boolean empty) {
1400                         if (getItem() == null ? item == null : getItem().equals(item)) {
1401                             rt_31200_count++;
1402                         }
1403                         super.updateItem(item, empty);
1404                         if (item == null || empty) {
1405                             view.setImage(null);
1406                             setText(null);
1407                         } else {
1408                             setText(item);
1409                         }
1410                     }
1411                 };
1412             }
1413         });
1414 
1415         StageLoader sl = new StageLoader(tableView);
1416 
1417         assertEquals(14, rt_31200_count);
1418 
1419         // resize the stage
1420         sl.getStage().setHeight(250);
1421         Toolkit.getToolkit().firePulse();
1422         sl.getStage().setHeight(50);
1423         Toolkit.getToolkit().firePulse();
1424         assertEquals(14, rt_31200_count);
1425 
1426         sl.dispose();
1427     }
1428 
1429     @Test public void test_rt_31200_tableRow() {
1430         rt_31200_count = 0;
1431         final TableView<Person> tableView = new TableView<Person>();
1432         tableView.setItems(FXCollections.observableArrayList(
1433                 new Person("Jacob", "Smith", "jacob.smith@example.com"),
1434                 new Person("Jim", "Bob", "jim.bob@example.com")
1435         ));
1436 
1437         TableColumn<Person,String> firstNameCol = new TableColumn("First Name");
1438         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
1439         tableView.getColumns().add(firstNameCol);
1440 
1441         tableView.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() {
1442             @Override
1443             public TableRow<Person> call(TableView<Person> param) {
1444                 return new TableRow<Person>() {
1445                     ImageView view = new ImageView();
1446                     { setGraphic(view); };
1447 
1448                     @Override
1449                     protected void updateItem(Person item, boolean empty) {
1450                         if (getItem() == null ? item == null : getItem().equals(item)) {
1451                             rt_31200_count++;
1452                         }
1453                         super.updateItem(item, empty);
1454                         if (item == null || empty) {
1455                             view.setImage(null);
1456                             setText(null);
1457                         } else {
1458                             setText(item.toString());
1459                         }
1460                     }
1461                 };
1462             }
1463         });
1464 
1465         StageLoader sl = new StageLoader(tableView);
1466 
1467         assertEquals(14, rt_31200_count);
1468 
1469         // resize the stage
1470         sl.getStage().setHeight(250);
1471         Toolkit.getToolkit().firePulse();
1472         sl.getStage().setHeight(50);
1473         Toolkit.getToolkit().firePulse();
1474         assertEquals(14, rt_31200_count);
1475 
1476         sl.dispose();
1477     }
1478 
1479     @Test public void test_rt_31727() {
1480         TableView table = new TableView();
1481         TableColumn<String,String> first = new TableColumn<String,String>("first");
1482         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1483         TableColumn<String,String> second = new TableColumn<String,String>("second");
1484         second.setCellValueFactory(new PropertyValueFactory("lastName"));
1485         table.getColumns().addAll(first, second);
1486 
1487         table.setItems(FXCollections.observableArrayList(
1488                 new Person("Jacob", "Smith", "jacob.smith@example.com"),
1489                 new Person("Jim", "Bob", "jim.bob@example.com")
1490         ));
1491 
1492         table.setEditable(true);
1493         first.setEditable(true);
1494 
1495         // do a normal edit
1496         table.edit(0, first);
1497         TablePosition editingCell = table.getEditingCell();
1498         assertNotNull(editingCell);
1499         assertEquals(0, editingCell.getRow());
1500         assertEquals(0, editingCell.getColumn());
1501         assertEquals(first, editingCell.getTableColumn());
1502         assertEquals(table, editingCell.getTableView());
1503 
1504         // cancel editing
1505         table.edit(-1, null);
1506         editingCell = table.getEditingCell();
1507         assertNull(editingCell);
1508     }
1509 
1510     @Test public void test_rt_21517() {
1511         TableView table = new TableView();
1512         TableColumn<String,String> first = new TableColumn<String,String>("first");
1513         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1514         TableColumn<String,String> second = new TableColumn<String,String>("second");
1515         second.setCellValueFactory(new PropertyValueFactory("lastName"));
1516         table.getColumns().addAll(first, second);
1517 
1518         Person aaa,bbb,ccc;
1519         table.setItems(FXCollections.observableArrayList(
1520                 aaa = new Person("AAA", "Smith", "jacob.smith@example.com"),
1521                 bbb = new Person("BBB", "Bob", "jim.bob@example.com"),
1522                 ccc = new Person("CCC", "Giles", "jim.bob@example.com")
1523         ));
1524 
1525         final TableView.TableViewSelectionModel sm = table.getSelectionModel();
1526 
1527         // test pre-conditions
1528         assertEquals(0, sm.getSelectedCells().size());
1529         assertEquals(0, sm.getSelectedItems().size());
1530         assertEquals(0, sm.getSelectedIndices().size());
1531 
1532         // select the 3rd row (that is, CCC)
1533         sm.select(2);
1534         assertTrue(sm.isSelected(2));
1535         assertEquals(2, sm.getSelectedIndex());
1536         assertEquals(1, sm.getSelectedIndices().size());
1537         assertTrue(sm.getSelectedIndices().contains(2));
1538         assertEquals(ccc, sm.getSelectedItem());
1539         assertEquals(1, sm.getSelectedItems().size());
1540         assertTrue(sm.getSelectedItems().contains(ccc));
1541 
1542         // we also want to test visually
1543         TableRow aaaRow = (TableRow) VirtualFlowTestUtils.getCell(table, 0);
1544         assertFalse(aaaRow.isSelected());
1545         TableRow cccRow = (TableRow) VirtualFlowTestUtils.getCell(table, 2);
1546         assertTrue(cccRow.isSelected());
1547 
1548         // sort tableview by firstname column in ascending (default) order
1549         // (so aaa continues to come first)
1550         table.getSortOrder().add(first);
1551 
1552         // nothing should have changed
1553         assertTrue(sm.isSelected(2));
1554         assertEquals(2, sm.getSelectedIndex());
1555         assertEquals(1, sm.getSelectedIndices().size());
1556         assertTrue(sm.getSelectedIndices().contains(2));
1557         assertEquals(ccc, sm.getSelectedItem());
1558         assertEquals(1, sm.getSelectedItems().size());
1559         assertTrue(sm.getSelectedItems().contains(ccc));
1560         aaaRow = (TableRow) VirtualFlowTestUtils.getCell(table, 0);
1561         assertFalse(aaaRow.isSelected());
1562         cccRow = (TableRow) VirtualFlowTestUtils.getCell(table, 2);
1563         assertTrue(cccRow.isSelected());
1564 
1565         // continue to sort tableview by firstname column, but now in descending
1566         // order, (so ccc to come first)
1567         first.setSortType(TableColumn.SortType.DESCENDING);
1568 
1569         // now test to ensure that CCC is still the only selected item, but now
1570         // located in index 0
1571         assertTrue(sm.isSelected(0));
1572         assertEquals(0, sm.getSelectedIndex());
1573         assertEquals(1, sm.getSelectedIndices().size());
1574         assertTrue(sm.getSelectedIndices().contains(0));
1575         assertEquals(ccc, sm.getSelectedItem());
1576         assertEquals(1, sm.getSelectedItems().size());
1577         assertTrue(sm.getSelectedItems().contains(ccc));
1578 
1579         // we also want to test visually
1580         aaaRow = (TableRow) VirtualFlowTestUtils.getCell(table, 1);
1581         assertFalse(aaaRow.isSelected());
1582         cccRow = (TableRow) VirtualFlowTestUtils.getCell(table, 0);
1583         assertTrue(cccRow.isSelected());
1584     }
1585 
1586     @Test public void test_rt_30484_tableCell() {
1587         TableView<Person> table = new TableView<>();
1588         TableColumn<Person,String> first = new TableColumn<>("first");
1589         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1590         table.getColumns().addAll(first);
1591 
1592         table.setItems(FXCollections.observableArrayList(
1593                 new Person("AAA", "Smith", "jacob.smith@example.com"),
1594                 new Person("BBB", "Bob", "jim.bob@example.com")
1595         ));
1596 
1597         first.setCellFactory(new Callback<TableColumn<Person, String>, TableCell<Person, String>>() {
1598             @Override
1599             public TableCell<Person, String> call(TableColumn<Person, String> param) {
1600                 return new TableCell<Person, String>() {
1601                     Rectangle graphic = new Rectangle(10, 10, Color.RED);
1602                     { setGraphic(graphic); };
1603 
1604                     @Override protected void updateItem(String item, boolean empty) {
1605                         super.updateItem(item, empty);
1606                         if (item == null || empty) {
1607                             graphic.setVisible(false);
1608                             setText(null);
1609                         } else {
1610                             graphic.setVisible(true);
1611                             setText(item);
1612                         }
1613                     }
1614                 };
1615             }
1616         });
1617 
1618         // First two rows have content, so the graphic should show.
1619         // All other rows have no content, so graphic should not show.
1620 
1621         VirtualFlowTestUtils.assertGraphicIsVisible(table,    0, 0);
1622         VirtualFlowTestUtils.assertGraphicIsVisible(table,    1, 0);
1623         VirtualFlowTestUtils.assertGraphicIsNotVisible(table, 2, 0);
1624         VirtualFlowTestUtils.assertGraphicIsNotVisible(table, 3, 0);
1625         VirtualFlowTestUtils.assertGraphicIsNotVisible(table, 4, 0);
1626         VirtualFlowTestUtils.assertGraphicIsNotVisible(table, 5, 0);
1627     }
1628 
1629     @Test public void test_rt_30484_tableRow() {
1630         TableView<Person> table = new TableView<>();
1631         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
1632         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1633         table.getColumns().addAll(first);
1634 
1635         table.setItems(FXCollections.observableArrayList(
1636                 new Person("AAA", "Smith", "jacob.smith@example.com"),
1637                 new Person("BBB", "Bob", "jim.bob@example.com")
1638         ));
1639 
1640         table.setRowFactory(new Callback<TableView<Person>, TableRow<Person>>() {
1641             @Override public TableRow<Person> call(TableView<Person> param) {
1642                 return new TableRow<Person>() {
1643                     Rectangle graphic = new Rectangle(10, 10, Color.RED);
1644                     { setGraphic(graphic); };
1645 
1646                     @Override protected void updateItem(Person item, boolean empty) {
1647                         super.updateItem(item, empty);
1648                         if (item == null || empty) {
1649                             graphic.setVisible(false);
1650                             setText(null);
1651                         } else {
1652                             graphic.setVisible(true);
1653                             setText(item.toString());
1654                         }
1655                     }
1656                 };
1657             }
1658         });
1659 
1660         // First two rows have content, so the graphic should show.
1661         // All other rows have no content, so graphic should not show.
1662 
1663         VirtualFlowTestUtils.assertGraphicIsVisible(table,    0);
1664         VirtualFlowTestUtils.assertGraphicIsVisible(table,    1);
1665         VirtualFlowTestUtils.assertGraphicIsNotVisible(table, 2);
1666         VirtualFlowTestUtils.assertGraphicIsNotVisible(table, 3);
1667         VirtualFlowTestUtils.assertGraphicIsNotVisible(table, 4);
1668         VirtualFlowTestUtils.assertGraphicIsNotVisible(table, 5);
1669     }
1670 
1671     @Test public void test_rt_32201() {
1672         final int numRows = 150;
1673         final int numColumns = 30;
1674 
1675         // create data
1676         final ObservableList<List<Object>> bigData = FXCollections.observableArrayList();
1677         for (int row = bigData.size(); row < numRows; row++) {
1678             List<Object> line = new ArrayList<>();
1679             for (int col = 0; col <= numColumns; col++) {
1680                 double value = (col == 0) ? (double)row : Math.random() * 1000;
1681                 line.add(value);
1682             }
1683             bigData.add(line);
1684         }
1685 
1686         TableView<List<Object>> table = new TableView<>(bigData);
1687 
1688         // create columns
1689         for (int i = 0; i <= numColumns; i++) {
1690             TableColumn<List<Object>,Object> col = new TableColumn<>("Col" + i);
1691             final int coli = i;
1692             col.setCellValueFactory(p -> new ReadOnlyObjectWrapper(p.getValue().get(coli)));
1693 
1694             table.getColumns().add(col);
1695         }
1696 
1697         // start test
1698         assertNull(table.getComparator());
1699         assertTrue(table.getSortOrder().isEmpty());
1700 
1701         table.getSortOrder().add(table.getColumns().get(0));
1702         assertNotNull(table.getComparator());
1703         assertTrue(table.getSortOrder().contains(table.getColumns().get(0)));
1704         assertEquals(1, table.getSortOrder().size());
1705 
1706         // remove column again
1707         try {
1708             table.getSortOrder().clear();
1709         } catch (ClassCastException e) {
1710             fail("Comparator should be null as the sort order list is empty.");
1711         }
1712         assertNull(table.getComparator());
1713         assertTrue(table.getSortOrder().isEmpty());
1714     }
1715 
1716     private int rt_31015_count = 0;
1717     @Test public void test_rt_31015() {
1718         TableView<Person> table = new TableView<>();
1719         table.setEditable(true);
1720         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
1721         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1722         table.getColumns().addAll(first);
1723 
1724         table.setItems(FXCollections.observableArrayList(
1725             new Person("John", "Smith", "jacob.smith@example.com")
1726         ));
1727 
1728         //Set cell factory for cells that allow editing
1729         Callback<TableColumn<Person,String>, TableCell<Person, String>> cellFactory = new Callback<TableColumn<Person,String>, TableCell<Person, String>>() {
1730             public TableCell<Person, String> call(TableColumn<Person, String> p) {
1731                 return new TableCell<Person, String>() {
1732                     @Override public void cancelEdit() {
1733                         super.cancelEdit();
1734                         rt_31015_count++;
1735                     }
1736                 };
1737             }
1738         };
1739         first.setCellFactory(cellFactory);
1740 
1741         StageLoader sl = new StageLoader(table);
1742 
1743         assertEquals(0, rt_31015_count);
1744 
1745         table.edit(0, first);
1746         assertEquals(0, rt_31015_count);
1747 
1748         table.edit(-1, null);
1749         assertEquals(1, rt_31015_count);
1750 
1751         sl.dispose();
1752     }
1753 
1754     @Test public void test_rt_31653() {
1755         TableView<Person> table = new TableView<>();
1756         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
1757         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1758         table.getColumns().addAll(first);
1759 
1760         table.setItems(FXCollections.observableArrayList(
1761                 new Person("John", "Smith", "jacob.smith@example.com")
1762         ));
1763 
1764         TableRow<Person> rowCell = (TableRow<Person>)VirtualFlowTestUtils.getCell(table, 0);
1765         final double initialWidth = rowCell.computePrefWidth(-1);
1766 
1767         first.setPrefWidth(200);
1768 
1769         final double newWidth = rowCell.computePrefWidth(-1);
1770         assertEquals(200, newWidth, 0.0);
1771         assertTrue(initialWidth != newWidth);
1772     }
1773 
1774     private int rt_29650_start_count = 0;
1775     private int rt_29650_commit_count = 0;
1776     private int rt_29650_cancel_count = 0;
1777     @Test public void test_rt_29650() {
1778         TableView<Person> table = new TableView<>();
1779         table.setEditable(true);
1780         table.setItems(FXCollections.observableArrayList(
1781                 new Person("John", "Smith", "jacob.smith@example.com")
1782         ));
1783 
1784         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
1785         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1786         Callback<TableColumn<Person, String>, TableCell<Person, String>> factory = TextFieldTableCell.forTableColumn();
1787         first.setCellFactory(factory);
1788         table.getColumns().addAll(first);
1789         first.setOnEditStart(t -> {
1790             rt_29650_start_count++;
1791         });
1792         first.setOnEditCommit(t -> {
1793             rt_29650_commit_count++;
1794         });
1795         first.setOnEditCancel(t -> {
1796             rt_29650_cancel_count++;
1797         });
1798 
1799         StageLoader sl = new StageLoader(table);
1800 
1801         table.edit(0, first);
1802 
1803         Toolkit.getToolkit().firePulse();
1804 
1805         TableCell rootCell = (TableCell) VirtualFlowTestUtils.getCell(table, 0, 0);
1806         TextField textField = (TextField) rootCell.getGraphic();
1807         textField.setText("Testing!");
1808         KeyEventFirer keyboard = new KeyEventFirer(textField);
1809         keyboard.doKeyPress(KeyCode.ENTER);
1810 
1811         // TODO should the following assert be enabled?
1812 //        assertEquals("Testing!", listView.getItems().get(0));
1813         assertEquals(1, rt_29650_start_count);
1814         assertEquals(1, rt_29650_commit_count);
1815         assertEquals(0, rt_29650_cancel_count);
1816 
1817         sl.dispose();
1818     }
1819 
1820     private int rt_29849_start_count = 0;
1821     @Test public void test_rt_29849() {
1822         TableView<Person> table = new TableView<>();
1823         table.setEditable(true);
1824         table.setItems(FXCollections.observableArrayList(
1825             new Person("John", "Smith", "jacob.smith@example.com")
1826         ));
1827 
1828         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
1829         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1830         Callback<TableColumn<Person, String>, TableCell<Person, String>> factory = TextFieldTableCell.forTableColumn();
1831         first.setCellFactory(factory);
1832         table.getColumns().addAll(first);
1833         first.setOnEditStart(t -> {
1834             rt_29849_start_count++;
1835         });
1836 
1837         // load the table so the default cells are created
1838         StageLoader sl = new StageLoader(table);
1839 
1840         // now replace the cell factory
1841         first.setCellFactory(TextFieldTableCell.<Person>forTableColumn());
1842 
1843         Toolkit.getToolkit().firePulse();
1844 
1845         // now start an edit and count the start edit events - it should be just 1
1846         table.edit(0, first);
1847         assertEquals(1, rt_29849_start_count);
1848 
1849         sl.dispose();
1850     }
1851 
1852     @Test public void test_rt_32708_removeFromColumnsList() {
1853         TableView<Person> table = new TableView<>();
1854         table.setEditable(true);
1855         table.setItems(FXCollections.observableArrayList(
1856                 new Person("John", "Smith", "jacob.smith@example.com")
1857         ));
1858 
1859         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
1860         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1861         TableColumn<Person,String> last = new TableColumn<Person,String>("last");
1862         first.setCellValueFactory(new PropertyValueFactory("lastName"));
1863         TableColumn<Person,String> email = new TableColumn<Person,String>("email");
1864         first.setCellValueFactory(new PropertyValueFactory("email"));
1865         table.getColumns().addAll(first, last, email);
1866 
1867         // load the table so the default cells are created
1868         StageLoader sl = new StageLoader(table);
1869 
1870         // test pre-conditions - last column should be visible
1871         VirtualFlowTestUtils.assertTableHeaderColumnExists(table, last, true);
1872 
1873         // remove last column from tableview
1874         table.getColumns().remove(last);
1875         Toolkit.getToolkit().firePulse();
1876 
1877         // test post conditions - last column should not be visible
1878         VirtualFlowTestUtils.assertTableHeaderColumnExists(table, last, false);
1879 
1880         sl.dispose();
1881     }
1882 
1883     @Test public void test_rt_32708_toggleVisible() {
1884         TableView<Person> table = new TableView<>();
1885         table.setEditable(true);
1886         table.setItems(FXCollections.observableArrayList(
1887                 new Person("John", "Smith", "jacob.smith@example.com")
1888         ));
1889 
1890         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
1891         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1892         TableColumn<Person,String> last = new TableColumn<Person,String>("last");
1893         first.setCellValueFactory(new PropertyValueFactory("lastName"));
1894         TableColumn<Person,String> email = new TableColumn<Person,String>("email");
1895         first.setCellValueFactory(new PropertyValueFactory("email"));
1896         table.getColumns().addAll(first, last, email);
1897 
1898         // load the table so the default cells are created
1899         StageLoader sl = new StageLoader(table);
1900 
1901         // test pre-conditions - last column should be visible
1902         VirtualFlowTestUtils.assertTableHeaderColumnExists(table, last, true);
1903 
1904         // hide the last column from tableview
1905         last.setVisible(false);
1906         Toolkit.getToolkit().firePulse();
1907 
1908         // test post conditions - last column should not be visible
1909         VirtualFlowTestUtils.assertTableHeaderColumnExists(table, last, false);
1910 
1911         sl.dispose();
1912     }
1913 
1914 //    @Ignore("Test started intermittently failing, most probably due to RT-36855 changeset")
1915     @Test public void test_rt_34493() {
1916         TableView<Person> table = new TableView<>();
1917         table.setItems(FXCollections.observableArrayList(
1918                 new Person("John", "Smith", "jacob.smith@example.com")
1919         ));
1920 
1921         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
1922         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1923         TableColumn<Person,String> last = new TableColumn<Person,String>("last");
1924         first.setCellValueFactory(new PropertyValueFactory("lastName"));
1925         TableColumn<Person,String> email = new TableColumn<Person,String>("email");
1926         first.setCellValueFactory(new PropertyValueFactory("email"));
1927         table.getColumns().addAll(first, last, email);
1928 
1929         // load the table
1930         StageLoader sl = new StageLoader(table);
1931 
1932         // resize the last column
1933         last.impl_setWidth(400);
1934         assertEquals(400, last.getWidth(), 0.0);
1935 
1936         // hide the first column
1937         table.getColumns().remove(first);
1938         Toolkit.getToolkit().firePulse();
1939 
1940         // the last column should still be 400px, not the default width or any
1941         // other value (based on the width of the content in that column)
1942         assertEquals(400, last.getWidth(), 0.0);
1943 
1944         sl.dispose();
1945     }
1946 
1947     @Test public void test_rt_34685_directEditCall_cellSelectionMode() {
1948         test_rt_34685_commitCount = 0;
1949         test_rt_34685(false, true);
1950     }
1951 
1952     @Test public void test_rt_34685_directEditCall_rowSelectionMode() {
1953         test_rt_34685_commitCount = 0;
1954         test_rt_34685(false, false);
1955     }
1956 
1957     @Test public void test_rt_34685_mouseDoubleClick_cellSelectionMode() {
1958         test_rt_34685_commitCount = 0;
1959         test_rt_34685(true, true);
1960     }
1961 
1962     @Test public void test_rt_34685_mouseDoubleClick_rowSelectionMode() {
1963         test_rt_34685_commitCount = 0;
1964         test_rt_34685(true, false);
1965     }
1966 
1967     private int test_rt_34685_commitCount = 0;
1968     private void test_rt_34685(boolean useMouseToInitiateEdit, boolean cellSelectionModeEnabled) {
1969         assertEquals(0, test_rt_34685_commitCount);
1970 
1971         TableView<Person> table = new TableView<>();
1972         table.getSelectionModel().setCellSelectionEnabled(cellSelectionModeEnabled);
1973         table.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
1974         table.setEditable(true);
1975 
1976         Person person1;
1977         table.setItems(FXCollections.observableArrayList(
1978             person1 = new Person("John", "Smith", "john.smith@example.com"),
1979             new Person("Jacob", "Michaels", "jacob.michaels@example.com"),
1980             new Person("Jim", "Bob", "jim.bob@example.com")
1981         ));
1982 
1983         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
1984         first.setCellValueFactory(new PropertyValueFactory("firstName"));
1985         Callback<TableColumn<Person, String>, TableCell<Person, String>> factory = TextFieldTableCell.forTableColumn();
1986         first.setCellFactory(factory);       // note that only the first name col is editable
1987 
1988         EventHandler<TableColumn.CellEditEvent<Person, String>> onEditCommit = first.getOnEditCommit();
1989         first.setOnEditCommit(event -> {
1990             test_rt_34685_commitCount++;
1991             onEditCommit.handle(event);
1992         });
1993 
1994         table.getColumns().addAll(first);
1995 
1996         // get the cell at (0,0)
1997         VirtualFlowTestUtils.BLOCK_STAGE_LOADER_DISPOSE = true;
1998         TableCell cell = (TableCell) VirtualFlowTestUtils.getCell(table, 0, 0);
1999         VirtualFlowTestUtils.BLOCK_STAGE_LOADER_DISPOSE = false;
2000         assertTrue(cell.getSkin() instanceof TableCellSkin);
2001         assertNull(cell.getGraphic());
2002         assertEquals("John", cell.getText());
2003         assertEquals("John", person1.getFirstName());
2004 
2005         // set the table to be editing the first cell at 0,0
2006         if (useMouseToInitiateEdit) {
2007             MouseEventFirer mouse = new MouseEventFirer(cell);
2008             mouse.fireMousePressAndRelease(2, 10, 10);  // click 10 pixels in and 10 pixels down
2009             mouse.dispose();
2010         } else {
2011             table.edit(0,first);
2012         }
2013 
2014         Toolkit.getToolkit().firePulse();
2015         assertNotNull(cell.getGraphic());
2016         assertTrue(cell.getGraphic() instanceof TextField);
2017 
2018         TextField textField = (TextField) cell.getGraphic();
2019         assertEquals("John", textField.getText());
2020 
2021         textField.setText("Andrew");
2022         textField.requestFocus();
2023         Toolkit.getToolkit().firePulse();
2024 
2025         KeyEventFirer keyboard = new KeyEventFirer(textField);
2026         keyboard.doKeyPress(KeyCode.ENTER);
2027 
2028         VirtualFlowTestUtils.getVirtualFlow(table).requestLayout();
2029         Toolkit.getToolkit().firePulse();
2030 
2031         VirtualFlowTestUtils.assertTableCellTextEquals(table, 0, 0, "Andrew");
2032         assertEquals("Andrew", cell.getText());
2033         assertEquals("Andrew", person1.getFirstName());
2034         assertEquals(1, test_rt_34685_commitCount);
2035     }
2036 
2037     @Test public void test_rt_35224() {
2038         TableView table = new TableView();
2039         TableColumn col1 = new TableColumn();
2040         TableColumn col2 = new TableColumn();
2041         table.getColumns().setAll(col1, col2);
2042 
2043         StageLoader sl = new StageLoader(table);
2044 
2045         Toolkit.getToolkit().firePulse();
2046         col1.getColumns().setAll(new TableColumn(), new TableColumn());
2047         Toolkit.getToolkit().firePulse();
2048         col2.getColumns().setAll(new TableColumn(), new TableColumn());
2049         Toolkit.getToolkit().firePulse();
2050 
2051         sl.dispose();
2052     }
2053 
2054     @Test public void test_rt_35141_simple_switch_two_columns_move_col1_forward_1_place() {
2055         TableView table = new TableView();
2056         TableColumn col1 = new TableColumn();
2057         TableColumn col2 = new TableColumn();
2058         table.getColumns().setAll(col1, col2);
2059 
2060         StageLoader sl = new StageLoader(table);
2061 
2062         assertEquals(0, getColumnIndex(col1));
2063         assertEquals(1, getColumnIndex(col2));
2064 
2065         moveColumn(col1, 1);
2066         assertEquals(1, getColumnIndex(col1));
2067         assertEquals(0, getColumnIndex(col2));
2068 
2069         sl.dispose();
2070     }
2071 
2072     @Test public void test_rt_35141_simple_switch_two_columns_move_col2_backward_1_place() {
2073         TableView table = new TableView();
2074         TableColumn col1 = new TableColumn();
2075         TableColumn col2 = new TableColumn();
2076         table.getColumns().setAll(col1, col2);
2077 
2078         StageLoader sl = new StageLoader(table);
2079 
2080         assertEquals(0, getColumnIndex(col1));
2081         assertEquals(1, getColumnIndex(col2));
2082 
2083         moveColumn(col2, 0);
2084         assertEquals(1, getColumnIndex(col1));
2085         assertEquals(0, getColumnIndex(col2));
2086 
2087         sl.dispose();
2088     }
2089 
2090     @Test public void test_rt_35141_simple_switch_three_columns_move_col1_forward_1_place() {
2091         TableView table = new TableView();
2092         TableColumn col1 = new TableColumn();
2093         TableColumn col2 = new TableColumn();
2094         TableColumn col3 = new TableColumn();
2095         table.getColumns().setAll(col1, col2, col3);
2096 
2097         StageLoader sl = new StageLoader(table);
2098 
2099         assertEquals(0, getColumnIndex(col1));
2100         assertEquals(1, getColumnIndex(col2));
2101         assertEquals(2, getColumnIndex(col3));
2102 
2103         moveColumn(col1, 1);
2104         assertEquals(1, getColumnIndex(col1));
2105         assertEquals(0, getColumnIndex(col2));
2106         assertEquals(2, getColumnIndex(col3));
2107 
2108         sl.dispose();
2109     }
2110 
2111     @Test public void test_rt_35141_simple_switch_three_columns_move_col2_backward_1_place() {
2112         TableView table = new TableView();
2113         TableColumn col1 = new TableColumn();
2114         TableColumn col2 = new TableColumn();
2115         TableColumn col3 = new TableColumn();
2116         table.getColumns().setAll(col1, col2, col3);
2117 
2118         StageLoader sl = new StageLoader(table);
2119 
2120         assertEquals(0, getColumnIndex(col1));
2121         assertEquals(1, getColumnIndex(col2));
2122         assertEquals(2, getColumnIndex(col3));
2123 
2124         moveColumn(col2, 0);
2125         assertEquals(1, getColumnIndex(col1));
2126         assertEquals(0, getColumnIndex(col2));
2127         assertEquals(2, getColumnIndex(col3));
2128 
2129         sl.dispose();
2130     }
2131 
2132     @Test public void test_rt_35141_simple_switch_three_columns_move_col2_forward_1_place() {
2133         TableView table = new TableView();
2134         TableColumn col1 = new TableColumn();
2135         TableColumn col2 = new TableColumn();
2136         TableColumn col3 = new TableColumn();
2137         table.getColumns().setAll(col1, col2, col3);
2138 
2139         StageLoader sl = new StageLoader(table);
2140 
2141         assertEquals(0, getColumnIndex(col1));
2142         assertEquals(1, getColumnIndex(col2));
2143         assertEquals(2, getColumnIndex(col3));
2144 
2145         moveColumn(col2, 2);
2146         assertEquals(0, getColumnIndex(col1));
2147         assertEquals(2, getColumnIndex(col2));
2148         assertEquals(1, getColumnIndex(col3));
2149 
2150         sl.dispose();
2151     }
2152 
2153     @Test public void test_rt_35141_simple_switch_three_columns_move_col3_backward_1_place() {
2154         TableView table = new TableView();
2155         TableColumn col1 = new TableColumn();
2156         TableColumn col2 = new TableColumn();
2157         TableColumn col3 = new TableColumn();
2158         table.getColumns().setAll(col1, col2, col3);
2159 
2160         StageLoader sl = new StageLoader(table);
2161 
2162         assertEquals(0, getColumnIndex(col1));
2163         assertEquals(1, getColumnIndex(col2));
2164         assertEquals(2, getColumnIndex(col3));
2165 
2166         moveColumn(col3, 1);
2167         assertEquals(0, getColumnIndex(col1));
2168         assertEquals(2, getColumnIndex(col2));
2169         assertEquals(1, getColumnIndex(col3));
2170 
2171         sl.dispose();
2172     }
2173 
2174     @Test public void test_rt_35141_simple_switch_three_columns_move_col0_forward_2_places() {
2175         TableView table = new TableView();
2176         TableColumn col1 = new TableColumn();
2177         TableColumn col2 = new TableColumn();
2178         TableColumn col3 = new TableColumn();
2179         table.getColumns().setAll(col1, col2, col3);
2180 
2181         StageLoader sl = new StageLoader(table);
2182 
2183         assertEquals(0, getColumnIndex(col1));
2184         assertEquals(1, getColumnIndex(col2));
2185         assertEquals(2, getColumnIndex(col3));
2186 
2187         moveColumn(col1, 2);
2188         assertEquals(2, getColumnIndex(col1));
2189         assertEquals(0, getColumnIndex(col2));
2190         assertEquals(1, getColumnIndex(col3));
2191 
2192         sl.dispose();
2193     }
2194 
2195     @Test public void test_rt_35141_simple_switch_three_columns_move_col3_backward_2_places() {
2196         TableView table = new TableView();
2197         TableColumn col1 = new TableColumn();
2198         TableColumn col2 = new TableColumn();
2199         TableColumn col3 = new TableColumn();
2200         table.getColumns().setAll(col1, col2, col3);
2201 
2202         StageLoader sl = new StageLoader(table);
2203 
2204         assertEquals(0, getColumnIndex(col1));
2205         assertEquals(1, getColumnIndex(col2));
2206         assertEquals(2, getColumnIndex(col3));
2207 
2208         moveColumn(col3, 0);
2209         assertEquals(1, getColumnIndex(col1));
2210         assertEquals(2, getColumnIndex(col2));
2211         assertEquals(0, getColumnIndex(col3));
2212 
2213         sl.dispose();
2214     }
2215 
2216     @Test public void test_rt_35141_hidden_column_move_col1_forward_1_place() {
2217         TableView table = new TableView();
2218         TableColumn col1 = new TableColumn();
2219         TableColumn col2 = new TableColumn();
2220         TableColumn col3 = new TableColumn();
2221         table.getColumns().setAll(col1, col2, col3);
2222 
2223         StageLoader sl = new StageLoader(table);
2224 
2225         col2.setVisible(false);
2226 
2227         assertEquals(0, getColumnIndex(col1));
2228         assertEquals(-1, getColumnIndex(col2));
2229         assertEquals(1, getColumnIndex(col3));
2230 
2231         moveColumn(col1, 1);
2232         assertEquals(1, getColumnIndex(col1));
2233         assertEquals(-1, getColumnIndex(col2));
2234         assertEquals(0, getColumnIndex(col3));
2235 
2236         sl.dispose();
2237     }
2238 
2239     @Test public void test_rt_35141_hidden_column_move_col1_forward_100_places() {
2240         TableView table = new TableView();
2241         TableColumn col1 = new TableColumn();
2242         TableColumn col2 = new TableColumn();
2243         TableColumn col3 = new TableColumn();
2244         table.getColumns().setAll(col1, col2, col3);
2245 
2246         StageLoader sl = new StageLoader(table);
2247 
2248         col2.setVisible(false);
2249 
2250         assertEquals(0, getColumnIndex(col1));
2251         assertEquals(-1, getColumnIndex(col2));
2252         assertEquals(1, getColumnIndex(col3));
2253 
2254         moveColumn(col1, 100);
2255         assertEquals(1, getColumnIndex(col1));
2256         assertEquals(-1, getColumnIndex(col2));
2257         assertEquals(0, getColumnIndex(col3));
2258 
2259         sl.dispose();
2260     }
2261 
2262     @Test public void test_rt_35141_hidden_column_move_col3_backward_1_place() {
2263         TableView table = new TableView();
2264         TableColumn col1 = new TableColumn();
2265         TableColumn col2 = new TableColumn();
2266         TableColumn col3 = new TableColumn();
2267         table.getColumns().setAll(col1, col2, col3);
2268 
2269         StageLoader sl = new StageLoader(table);
2270 
2271         col2.setVisible(false);
2272 
2273         assertEquals(0, getColumnIndex(col1));
2274         assertEquals(-1, getColumnIndex(col2));
2275         assertEquals(1, getColumnIndex(col3));
2276 
2277         moveColumn(col3, 0);
2278         assertEquals(1, getColumnIndex(col1));
2279         assertEquals(-1, getColumnIndex(col2));
2280         assertEquals(0, getColumnIndex(col3));
2281 
2282         sl.dispose();
2283     }
2284 
2285     @Test public void test_rt_35141_multiple_hidden_columns_move_col1_to_middle() {
2286         TableView table = new TableView();
2287         TableColumn col1 = new TableColumn();
2288         TableColumn col2 = new TableColumn();
2289         TableColumn col3 = new TableColumn();
2290         TableColumn col4 = new TableColumn();
2291         TableColumn col5 = new TableColumn();
2292         TableColumn col6 = new TableColumn();
2293         table.getColumns().setAll(col1, col2, col3, col4, col5, col6);
2294 
2295         StageLoader sl = new StageLoader(table);
2296 
2297         col2.setVisible(false);
2298         col4.setVisible(false);
2299 
2300         assertEquals(0, getColumnIndex(col1));
2301         assertEquals(-1, getColumnIndex(col2));
2302         assertEquals(1, getColumnIndex(col3));
2303         assertEquals(-1, getColumnIndex(col4));
2304         assertEquals(2, getColumnIndex(col5));
2305         assertEquals(3, getColumnIndex(col6));
2306 
2307         moveColumn(col1, 1);    // 1 should represent the spot between col2 and col4
2308         assertEquals(1, getColumnIndex(col1));
2309         assertEquals(-1, getColumnIndex(col2));
2310         assertEquals(0, getColumnIndex(col3));
2311         assertEquals(-1, getColumnIndex(col4));
2312         assertEquals(2, getColumnIndex(col5));
2313         assertEquals(3, getColumnIndex(col6));
2314 
2315         sl.dispose();
2316     }
2317 
2318     @Test public void test_rt_35141_multiple_hidden_columns_move_col1_to_end() {
2319         TableView table = new TableView();
2320         TableColumn col1 = new TableColumn();
2321         TableColumn col2 = new TableColumn();
2322         TableColumn col3 = new TableColumn();
2323         TableColumn col4 = new TableColumn();
2324         TableColumn col5 = new TableColumn();
2325         TableColumn col6 = new TableColumn();
2326         table.getColumns().setAll(col1, col2, col3, col4, col5, col6);
2327 
2328         StageLoader sl = new StageLoader(table);
2329 
2330         col2.setVisible(false);
2331         col4.setVisible(false);
2332 
2333         assertEquals(0, getColumnIndex(col1));
2334         assertEquals(-1, getColumnIndex(col2));
2335         assertEquals(1, getColumnIndex(col3));
2336         assertEquals(-1, getColumnIndex(col4));
2337         assertEquals(2, getColumnIndex(col5));
2338         assertEquals(3, getColumnIndex(col6));
2339 
2340         moveColumn(col1, 3);    // 3 should represent the end place
2341         assertEquals(3, getColumnIndex(col1));
2342         assertEquals(-1, getColumnIndex(col2));
2343         assertEquals(0, getColumnIndex(col3));
2344         assertEquals(-1, getColumnIndex(col4));
2345         assertEquals(1, getColumnIndex(col5));
2346         assertEquals(2, getColumnIndex(col6));
2347 
2348         sl.dispose();
2349     }
2350 
2351     @Test public void test_rt_35141_multiple_hidden_columns_move_col3_to_start() {
2352         TableView table = new TableView();
2353         TableColumn col1 = new TableColumn();
2354         TableColumn col2 = new TableColumn();
2355         TableColumn col3 = new TableColumn();
2356         TableColumn col4 = new TableColumn();
2357         TableColumn col5 = new TableColumn();
2358         TableColumn col6 = new TableColumn();
2359         table.getColumns().setAll(col1, col2, col3, col4, col5, col6);
2360 
2361         StageLoader sl = new StageLoader(table);
2362 
2363         col2.setVisible(false);
2364         col4.setVisible(false);
2365 
2366         assertEquals(0, getColumnIndex(col1));
2367         assertEquals(-1, getColumnIndex(col2));
2368         assertEquals(1, getColumnIndex(col3));
2369         assertEquals(-1, getColumnIndex(col4));
2370         assertEquals(2, getColumnIndex(col5));
2371         assertEquals(3, getColumnIndex(col6));
2372 
2373         moveColumn(col3, 0);
2374         assertEquals(1, getColumnIndex(col1));
2375         assertEquals(-1, getColumnIndex(col2));
2376         assertEquals(0, getColumnIndex(col3));
2377         assertEquals(-1, getColumnIndex(col4));
2378         assertEquals(2, getColumnIndex(col5));
2379         assertEquals(3, getColumnIndex(col6));
2380 
2381         sl.dispose();
2382     }
2383 
2384     @Test public void test_rt_35141_multiple_hidden_columns_move_col3_to_end() {
2385         TableView table = new TableView();
2386         TableColumn col1 = new TableColumn();
2387         TableColumn col2 = new TableColumn();
2388         TableColumn col3 = new TableColumn();
2389         TableColumn col4 = new TableColumn();
2390         TableColumn col5 = new TableColumn();
2391         TableColumn col6 = new TableColumn();
2392         table.getColumns().setAll(col1, col2, col3, col4, col5, col6);
2393 
2394         StageLoader sl = new StageLoader(table);
2395 
2396         col2.setVisible(false);
2397         col4.setVisible(false);
2398 
2399         assertEquals(0, getColumnIndex(col1));
2400         assertEquals(-1, getColumnIndex(col2));
2401         assertEquals(1, getColumnIndex(col3));
2402         assertEquals(-1, getColumnIndex(col4));
2403         assertEquals(2, getColumnIndex(col5));
2404         assertEquals(3, getColumnIndex(col6));
2405 
2406         moveColumn(col3, 3);    // 3 should represent the end place
2407         assertEquals(0, getColumnIndex(col1));
2408         assertEquals(-1, getColumnIndex(col2));
2409         assertEquals(3, getColumnIndex(col3));
2410         assertEquals(-1, getColumnIndex(col4));
2411         assertEquals(1, getColumnIndex(col5));
2412         assertEquals(2, getColumnIndex(col6));
2413 
2414         sl.dispose();
2415     }
2416 
2417     @Test public void test_rt_35141_multiple_hidden_columns_move_col6_to_start() {
2418         TableView table = new TableView();
2419         TableColumn col1 = new TableColumn();
2420         TableColumn col2 = new TableColumn();
2421         TableColumn col3 = new TableColumn();
2422         TableColumn col4 = new TableColumn();
2423         TableColumn col5 = new TableColumn();
2424         TableColumn col6 = new TableColumn();
2425         table.getColumns().setAll(col1, col2, col3, col4, col5, col6);
2426 
2427         StageLoader sl = new StageLoader(table);
2428 
2429         col2.setVisible(false);
2430         col4.setVisible(false);
2431 
2432         assertEquals(0, getColumnIndex(col1));
2433         assertEquals(-1, getColumnIndex(col2));
2434         assertEquals(1, getColumnIndex(col3));
2435         assertEquals(-1, getColumnIndex(col4));
2436         assertEquals(2, getColumnIndex(col5));
2437         assertEquals(3, getColumnIndex(col6));
2438 
2439         moveColumn(col6, 0);
2440         assertEquals(1, getColumnIndex(col1));
2441         assertEquals(-1, getColumnIndex(col2));
2442         assertEquals(2, getColumnIndex(col3));
2443         assertEquals(-1, getColumnIndex(col4));
2444         assertEquals(3, getColumnIndex(col5));
2445         assertEquals(0, getColumnIndex(col6));
2446 
2447         sl.dispose();
2448     }
2449 
2450     @Test public void test_rt_35141_multiple_hidden_columns_move_col6_to_middle() {
2451         TableView table = new TableView();
2452         TableColumn col1 = new TableColumn();
2453         TableColumn col2 = new TableColumn();
2454         TableColumn col3 = new TableColumn();
2455         TableColumn col4 = new TableColumn();
2456         TableColumn col5 = new TableColumn();
2457         TableColumn col6 = new TableColumn();
2458         table.getColumns().setAll(col1, col2, col3, col4, col5, col6);
2459 
2460         StageLoader sl = new StageLoader(table);
2461 
2462         col2.setVisible(false);
2463         col4.setVisible(false);
2464 
2465         assertEquals(0, getColumnIndex(col1));
2466         assertEquals(-1, getColumnIndex(col2));
2467         assertEquals(1, getColumnIndex(col3));
2468         assertEquals(-1, getColumnIndex(col4));
2469         assertEquals(2, getColumnIndex(col5));
2470         assertEquals(3, getColumnIndex(col6));
2471 
2472         moveColumn(col6, 1);
2473         assertEquals(0, getColumnIndex(col1));
2474         assertEquals(-1, getColumnIndex(col2));
2475         assertEquals(2, getColumnIndex(col3));
2476         assertEquals(-1, getColumnIndex(col4));
2477         assertEquals(3, getColumnIndex(col5));
2478         assertEquals(1, getColumnIndex(col6));
2479 
2480         sl.dispose();
2481     }
2482 
2483     @Test public void test_rt_34042() {
2484         Scene scene = new Scene(new Group());
2485         SplitPane splitPane = new SplitPane();
2486         splitPane.setOrientation(Orientation.VERTICAL);
2487 
2488         //TREETABLECOLUMN
2489         TreeTableView<Person> treeTableView = new TreeTableView<>();
2490         TreeTableColumn temp = new TreeTableColumn("First Name");
2491         temp.setMinWidth(100);
2492         temp.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
2493 
2494         TreeTableColumn temp2 = new TreeTableColumn("Last Name");
2495         temp2.setMinWidth(100);
2496         temp2.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
2497 
2498         TreeTableColumn temp3 = new TreeTableColumn("Email");
2499         temp3.setMinWidth(200);
2500         temp3.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
2501 
2502         treeTableView.getColumns().addAll(temp, temp2, temp3);
2503 
2504         //TABLE
2505         TableView<Person> table = new TableView<Person>();
2506         table.setEditable(true);
2507         table.getSelectionModel().setCellSelectionEnabled(true);
2508         TableColumn firstNameCol = new TableColumn("First Name");
2509         firstNameCol.setMinWidth(100);
2510         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
2511 
2512         TableColumn lastNameCol = new TableColumn("Last Name");
2513         lastNameCol.setMinWidth(100);
2514         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
2515 
2516         TableColumn emailCol = new TableColumn("Email");
2517         emailCol.setMinWidth(200);
2518         emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
2519 
2520         table.setItems(personTestData);
2521         table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
2522 
2523         splitPane.getItems().add(treeTableView);
2524         splitPane.getItems().add(table);
2525 
2526         ((Group) scene.getRoot()).getChildren().addAll(splitPane);
2527 
2528         new StageLoader(scene);
2529 
2530         TableView.TableViewSelectionModel sm = table.getSelectionModel();
2531         sm.select(2, lastNameCol);
2532         assertFalse(sm.isSelected(2, firstNameCol));
2533         assertTrue(sm.isSelected(2, lastNameCol));
2534         assertFalse(sm.isSelected(2, emailCol));
2535 
2536         KeyEventFirer keyboard = new KeyEventFirer(table);
2537         keyboard.doKeyPress(KeyCode.LEFT);
2538         assertTrue(sm.isSelected(2, firstNameCol));
2539         assertFalse(sm.isSelected(2, lastNameCol));
2540         assertFalse(sm.isSelected(2, emailCol));
2541 
2542         keyboard.doKeyPress(KeyCode.RIGHT);
2543         assertFalse(sm.isSelected(2, firstNameCol));
2544         assertTrue(sm.isSelected(2, lastNameCol));
2545         assertFalse(sm.isSelected(2, emailCol));
2546 
2547         keyboard.doKeyPress(KeyCode.RIGHT);
2548         assertFalse(sm.isSelected(2, firstNameCol));
2549         assertFalse(sm.isSelected(2, lastNameCol));
2550         assertTrue(sm.isSelected(2, emailCol));
2551     }
2552 
2553     @Test public void test_rt35039() {
2554         final List<String> data = new ArrayList<>();
2555         data.add("aabbaa");
2556         data.add("bbc");
2557 
2558         final TableView<String> tableView = new TableView<>();
2559         tableView.setItems(FXCollections.observableArrayList(data));
2560 
2561         StageLoader sl = new StageLoader(tableView);
2562 
2563         // selection starts off at row -1
2564         assertNull(tableView.getSelectionModel().getSelectedItem());
2565 
2566         // select "bbc" and ensure everything is set to that
2567         tableView.getSelectionModel().select(1);
2568         assertEquals("bbc", tableView.getSelectionModel().getSelectedItem());
2569 
2570         // change the items list - but retain the same content. We expect
2571         // that "bbc" remains selected as it is still in the list
2572         tableView.setItems(FXCollections.observableArrayList(data));
2573         assertEquals("bbc", tableView.getSelectionModel().getSelectedItem());
2574 
2575         sl.dispose();
2576     }
2577 
2578     @Test public void test_rt35763_observableList() {
2579         TableView<Person> table = new TableView();
2580 
2581         TableColumn firstNameCol = new TableColumn("First Name");
2582         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
2583         table.getColumns().add(firstNameCol);
2584 
2585         Person jacob, isabella, ethan, emma, michael;
2586         table.setItems(FXCollections.observableArrayList(
2587                 jacob = new Person("Jacob", "Smith", "jacob.smith@example.com"),
2588                 isabella = new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
2589                 ethan = new Person("Ethan", "Williams", "ethan.williams@example.com"),
2590                 emma = new Person("Emma", "Jones", "emma.jones@example.com"),
2591                 michael = new Person("Michael", "Brown", "michael.brown@example.com")));
2592 
2593         assertEquals(jacob, table.getItems().get(0));
2594         assertEquals(isabella, table.getItems().get(1));
2595         assertEquals(ethan, table.getItems().get(2));
2596         assertEquals(emma, table.getItems().get(3));
2597         assertEquals(michael, table.getItems().get(4));
2598 
2599         // change sort order - expect items to be sorted
2600         table.getSortOrder().setAll(firstNameCol);
2601 
2602         assertEquals(jacob, table.getItems().get(3));
2603         assertEquals(isabella, table.getItems().get(2));
2604         assertEquals(ethan, table.getItems().get(1));
2605         assertEquals(emma, table.getItems().get(0));
2606         assertEquals(michael, table.getItems().get(4));
2607 
2608         // set new items into items list - expect sortOrder list to be reset
2609         // and the items list to remain unsorted
2610         table.setItems(FXCollections.observableArrayList(
2611                 jacob = new Person("Jacob", "Smith", "jacob.smith@example.com"),
2612                 isabella = new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
2613                 ethan = new Person("Ethan", "Williams", "ethan.williams@example.com"),
2614                 emma = new Person("Emma", "Jones", "emma.jones@example.com"),
2615                 michael = new Person("Michael", "Brown", "michael.brown@example.com")));
2616 
2617         assertEquals(jacob, table.getItems().get(0));
2618         assertEquals(isabella, table.getItems().get(1));
2619         assertEquals(ethan, table.getItems().get(2));
2620         assertEquals(emma, table.getItems().get(3));
2621         assertEquals(michael, table.getItems().get(4));
2622 
2623         assertTrue(table.getSortOrder().isEmpty());
2624     }
2625 
2626     @Test public void test_rt35763_sortedList() {
2627         TableView<Person> table = new TableView();
2628 
2629         TableColumn firstNameCol = new TableColumn("First Name");
2630         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
2631         table.getColumns().add(firstNameCol);
2632 
2633         Person jacob, isabella, ethan, emma, michael;
2634         SortedList<Person> sortedList = new SortedList<>(FXCollections.observableArrayList(
2635                 jacob = new Person("Jacob", "Smith", "jacob.smith@example.com"),
2636                 isabella = new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
2637                 ethan = new Person("Ethan", "Williams", "ethan.williams@example.com"),
2638                 emma = new Person("Emma", "Jones", "emma.jones@example.com"),
2639                 michael = new Person("Michael", "Brown", "michael.brown@example.com")));
2640         sortedList.comparatorProperty().bind(table.comparatorProperty());
2641         table.setItems(sortedList);
2642 
2643         assertEquals(jacob, table.getItems().get(0));
2644         assertEquals(isabella, table.getItems().get(1));
2645         assertEquals(ethan, table.getItems().get(2));
2646         assertEquals(emma, table.getItems().get(3));
2647         assertEquals(michael, table.getItems().get(4));
2648 
2649         // change sort order - expect items to be sorted
2650         table.getSortOrder().setAll(firstNameCol);
2651 
2652         assertEquals(jacob, table.getItems().get(3));
2653         assertEquals(isabella, table.getItems().get(2));
2654         assertEquals(ethan, table.getItems().get(1));
2655         assertEquals(emma, table.getItems().get(0));
2656         assertEquals(michael, table.getItems().get(4));
2657 
2658         // set new items into items list - expect sortOrder list to be retained
2659         // as we're inserting a SortedList
2660         SortedList<Person> sortedList2 = new SortedList<>(FXCollections.observableArrayList(
2661                 jacob = new Person("Jacob", "Smith", "jacob.smith@example.com"),
2662                 isabella = new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
2663                 ethan = new Person("Ethan", "Williams", "ethan.williams@example.com"),
2664                 emma = new Person("Emma", "Jones", "emma.jones@example.com"),
2665                 michael = new Person("Michael", "Brown", "michael.brown@example.com")));
2666         sortedList2.comparatorProperty().bind(table.comparatorProperty());
2667         table.setItems(sortedList2);
2668 
2669         assertEquals(jacob, table.getItems().get(3));
2670         assertEquals(isabella, table.getItems().get(2));
2671         assertEquals(ethan, table.getItems().get(1));
2672         assertEquals(emma, table.getItems().get(0));
2673         assertEquals(michael, table.getItems().get(4));
2674 
2675         assertEquals(1, table.getSortOrder().size());
2676         assertEquals(firstNameCol, table.getSortOrder().get(0));
2677     }
2678 
2679     @Test(expected = IndexOutOfBoundsException.class)
2680     public void test_rt35768_negativeFrom() {
2681         readOnlyUnbackedObservableListSubListTest(-1, 0);
2682     }
2683 
2684     @Test(expected = IndexOutOfBoundsException.class)
2685     public void test_rt35768_bigTo() {
2686         readOnlyUnbackedObservableListSubListTest(0, 10);
2687     }
2688 
2689     @Test(expected = IndexOutOfBoundsException.class)
2690     public void test_rt35768_fromEqualsTo() {
2691         readOnlyUnbackedObservableListSubListTest(1, 1);
2692     }
2693 
2694     private void readOnlyUnbackedObservableListSubListTest(int from, int to) {
2695         final SelectedCellsMap<TablePosition> selectedCellsMap = new SelectedCellsMap<TablePosition>(c -> { /* Do nothing */}) {
2696             @Override public boolean isCellSelectionEnabled() {
2697                 return false;
2698             }
2699         };
2700         ReadOnlyUnbackedObservableList<TablePosition<Object, ?>> selectedCellsSeq = new ReadOnlyUnbackedObservableList<TablePosition<Object, ?>>() {
2701             @Override public TablePosition<Object, ?> get(int i) {
2702                 return selectedCellsMap.get(i);
2703             }
2704 
2705             @Override public int size() {
2706                 return selectedCellsMap.size();
2707             }
2708         };
2709 
2710         // This should result in an IOOBE, but didn't until this bug was fixed
2711         selectedCellsSeq.subList(from, to);
2712     }
2713 
2714     @Test public void test_rt35857() {
2715         ObservableList<String> fxList = FXCollections.observableArrayList("A", "B", "C");
2716         final TableView<String> tableView = new TableView<String>(fxList);
2717 
2718         tableView.getSelectionModel().select(0);
2719 
2720         ObservableList<String> selectedItems = tableView.getSelectionModel().getSelectedItems();
2721         assertEquals(1, selectedItems.size());
2722         assertEquals("A", selectedItems.get(0));
2723 
2724         tableView.getItems().removeAll(selectedItems);
2725         assertEquals(2, fxList.size());
2726         assertEquals("B", fxList.get(0));
2727         assertEquals("C", fxList.get(1));
2728     }
2729 
2730     @Test public void test_getColumnHeaderForColumn() {
2731         TableView<Person> table = new TableView<>();
2732         table.setItems(FXCollections.observableArrayList(
2733                 new Person("John", "Smith", "jacob.smith@example.com")
2734         ));
2735 
2736         TableColumn<Person,String> first = new TableColumn<Person,String>("first");
2737         first.setCellValueFactory(new PropertyValueFactory("firstName"));
2738         TableColumn<Person,String> last = new TableColumn<Person,String>("last");
2739         first.setCellValueFactory(new PropertyValueFactory("lastName"));
2740 
2741         TableColumn name = new TableColumn("Name");
2742         name.getColumns().addAll(first, last);
2743 
2744         table.getColumns().setAll(name);
2745 
2746         StageLoader sl = new StageLoader(table);
2747 
2748         TableHeaderRow headerRow = VirtualFlowTestUtils.getTableHeaderRow(table);
2749 
2750         TableColumnHeader nameHeader = TableHeaderRowRetriever.getColumnHeaderFor(headerRow, name);
2751         TableColumnHeader firstHeader = TableHeaderRowRetriever.getColumnHeaderFor(headerRow, first);
2752         TableColumnHeader lastHeader = TableHeaderRowRetriever.getColumnHeaderFor(headerRow, last);
2753         assertNotNull(nameHeader);
2754         assertEquals(name, nameHeader.getTableColumn());
2755         assertNotNull(firstHeader);
2756         assertEquals(first, firstHeader.getTableColumn());
2757         assertNotNull(lastHeader);
2758         assertEquals(last, lastHeader.getTableColumn());
2759 
2760         sl.dispose();
2761     }
2762 
2763     @Test public void test_rt36220() {
2764         ObservableList<AtomicLong> tableItems = FXCollections.observableArrayList();
2765         tableItems.add(new AtomicLong(0L));
2766 
2767         TableView<AtomicLong> tableView = new TableView<>();
2768         tableView.getItems().setAll(tableItems);
2769 
2770         TableColumn<AtomicLong, String> col = new TableColumn<>();
2771         col.setCellValueFactory(obj -> new SimpleStringProperty(String.valueOf(obj.getValue().longValue())));
2772         col.setPrefWidth(180);
2773         tableView.getColumns().add(col);
2774 
2775         new StageLoader(tableView);
2776 
2777         VirtualFlowTestUtils.assertTableCellTextEquals(tableView, 0, 0, "0");
2778 
2779         // 1) using this trick will prevent the first update
2780         col.setMinWidth(col.getPrefWidth() + 1);
2781 
2782         long expected = System.currentTimeMillis();
2783         tableItems.get(0).set(expected);
2784         tableView.getItems().setAll(tableItems);
2785 
2786         Toolkit.getToolkit().firePulse();
2787 
2788         VirtualFlowTestUtils.assertTableCellTextEquals(tableView, 0, 0, ""+expected);
2789     }
2790 
2791     @Test public void test_rt36425() {
2792         TableView<String> tableView = new TableView<>();
2793 
2794         TableColumn<String, String> tableColumn = new TableColumn<>();
2795         tableColumn.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()));
2796         tableColumn.setText("Test");
2797         tableView.getColumns().add(tableColumn);
2798 
2799         SimpleListProperty<String> data = new SimpleListProperty<>(FXCollections.observableArrayList());
2800         tableView.itemsProperty().bind(data);
2801         data.addAll("AAA", "BBB");
2802 
2803         assertEquals("AAA", data.get(0));
2804         assertEquals("BBB", data.get(1));
2805 
2806         tableView.getSortOrder().add(tableColumn);
2807 
2808         assertTrue(tableView.getSortOrder().contains(tableColumn));
2809         assertEquals("AAA", data.get(0));
2810         assertEquals("BBB", data.get(1));
2811 
2812         tableColumn.setSortType(TableColumn.SortType.DESCENDING);
2813         assertEquals("AAA", data.get(1));
2814         assertEquals("BBB", data.get(0));
2815     }
2816 
2817     private int test_rt_36353_selectedItemCount = 0;
2818     private int test_rt_36353_selectedIndexCount = 0;
2819     @Test public void test_rt36353() {
2820         ObservableList<String> data = FXCollections.observableArrayList();
2821         data.addAll("2", "1", "3");
2822         SortedList<String> sortedList = new SortedList<>(data);
2823 
2824         TableView<String> tableView = new TableView<>(sortedList);
2825         sortedList.comparatorProperty().bind(tableView.comparatorProperty());
2826 
2827         TableColumn<String, String> tableColumn = new TableColumn<>();
2828         tableColumn.setCellValueFactory(rowValue -> new SimpleStringProperty(rowValue.getValue()));
2829         tableColumn.setText("Test");
2830         tableView.getColumns().add(tableColumn);
2831 
2832         tableView.getSelectionModel().selectedItemProperty().addListener((e, oldSelection, newSelection) -> {
2833             test_rt_36353_selectedItemCount++;
2834         });
2835         tableView.getSelectionModel().selectedIndexProperty().addListener((e, oldIndex, newIndex) -> {
2836             test_rt_36353_selectedIndexCount++;
2837         });
2838 
2839         assertEquals(0, test_rt_36353_selectedItemCount);
2840         assertEquals(0, test_rt_36353_selectedIndexCount);
2841 
2842         tableView.getSelectionModel().select(1);
2843         assertEquals(1, test_rt_36353_selectedItemCount);
2844         assertEquals(1, test_rt_36353_selectedIndexCount);
2845         assertEquals("2", sortedList.get(0));
2846         assertEquals("1", sortedList.get(1));
2847         assertEquals("3", sortedList.get(2));
2848 
2849         tableView.getSortOrder().add(tableColumn);
2850         assertEquals(1, test_rt_36353_selectedItemCount);
2851         assertEquals(2, test_rt_36353_selectedIndexCount);
2852         assertEquals("1", sortedList.get(0));
2853         assertEquals("2", sortedList.get(1));
2854         assertEquals("3", sortedList.get(2));
2855 
2856         tableColumn.setSortType(TableColumn.SortType.DESCENDING);
2857         assertEquals(1, test_rt_36353_selectedItemCount);
2858         assertEquals(3, test_rt_36353_selectedIndexCount);
2859         assertEquals("3", sortedList.get(0));
2860         assertEquals("2", sortedList.get(1));
2861         assertEquals("1", sortedList.get(2));
2862 
2863         tableView.getSortOrder().remove(tableColumn);
2864         assertEquals(1, test_rt_36353_selectedItemCount);
2865         assertEquals(4, test_rt_36353_selectedIndexCount);
2866         assertEquals("2", sortedList.get(0));
2867         assertEquals("1", sortedList.get(1));
2868         assertEquals("3", sortedList.get(2));
2869     }
2870 
2871     // This test ensures that we reuse column headers when the columns still
2872     // exist after a change to the columns list - rather than recreating new
2873     // column headers. The issue in RT-36290 was that we were creating new column
2874     // headers that were then in their initial states, allowing them to call
2875     // TableColumnHeader#updateScene(), which would resize the column based on the
2876     // data within it.
2877     @Test public void test_rt36290() {
2878         TableView<String> tableView = new TableView<>();
2879 
2880         TableColumn<String, String> tableColumn1 = new TableColumn<>();
2881         tableColumn1.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()));
2882         tableColumn1.setText("Test1");
2883 
2884         TableColumn<String, String> tableColumn2 = new TableColumn<>();
2885         tableColumn2.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()));
2886         tableColumn2.setText("Test2");
2887 
2888         tableView.getColumns().setAll(tableColumn1, tableColumn2);
2889 
2890         StageLoader sl = new StageLoader(tableView);
2891 
2892         final TableColumnHeader header1 = VirtualFlowTestUtils.getTableColumnHeader(tableView, tableColumn1);
2893         final TableColumnHeader header2 = VirtualFlowTestUtils.getTableColumnHeader(tableView, tableColumn2);
2894 
2895         tableView.getColumns().setAll(tableColumn2, tableColumn1);
2896         Toolkit.getToolkit().firePulse();
2897 
2898         final TableColumnHeader header1_after = VirtualFlowTestUtils.getTableColumnHeader(tableView, tableColumn1);
2899         final TableColumnHeader header2_after = VirtualFlowTestUtils.getTableColumnHeader(tableView, tableColumn2);
2900 
2901         assertEquals(header1, header1_after);
2902         assertEquals(header2, header2_after);
2903 
2904         sl.dispose();
2905     }
2906 
2907     @Test public void test_rt25679_rowSelection() {
2908         test_rt25679(true);
2909     }
2910 
2911     @Test public void test_rt25679_cellSelection() {
2912         test_rt25679(false);
2913     }
2914 
2915     private void test_rt25679(boolean rowSelection) {
2916         Button focusBtn = new Button("Focus here");
2917 
2918         TableView<String> tableView = new TableView<>(FXCollections.observableArrayList("A", "B", "C"));
2919 
2920         TableColumn<String, String> tableColumn = new TableColumn<>();
2921         tableColumn.setCellValueFactory(rowValue -> new SimpleStringProperty(rowValue.getValue()));
2922         tableView.getColumns().add(tableColumn);
2923         TableView.TableViewSelectionModel<String> sm = tableView.getSelectionModel();
2924         sm.setCellSelectionEnabled(! rowSelection);
2925 
2926         VBox vbox = new VBox(focusBtn, tableView);
2927 
2928         StageLoader sl = new StageLoader(vbox);
2929         sl.getStage().requestFocus();
2930         focusBtn.requestFocus();
2931         Toolkit.getToolkit().firePulse();
2932 
2933         // test initial state
2934         assertEquals(sl.getStage().getScene().getFocusOwner(), focusBtn);
2935         assertTrue(focusBtn.isFocused());
2936         assertEquals(-1, sm.getSelectedIndex());
2937         assertNull(sm.getSelectedItem());
2938 
2939         // move focus to the tableView
2940         tableView.requestFocus();
2941 
2942         // ensure that there is a selection (where previously there was not one)
2943         assertEquals(sl.getStage().getScene().getFocusOwner(), tableView);
2944         assertTrue(tableView.isFocused());
2945 
2946         if (rowSelection) {
2947             assertEquals(0, sm.getSelectedIndices().size());
2948             assertNull(sm.getSelectedItem());
2949             assertFalse(sm.isSelected(0));
2950             assertEquals(0, sm.getSelectedCells().size());
2951         } else {
2952             assertFalse(sm.isSelected(0, tableColumn));
2953             assertEquals(0, sm.getSelectedCells().size());
2954         }
2955 
2956         sl.dispose();
2957     }
2958 
2959     private int rt36556_instanceCount;
2960     @Test public void test_rt36556_scrollTo() {
2961         rt36556_instanceCount = 0;
2962 
2963         TableView<String> tableView = new TableView<>();
2964         tableView.setRowFactory(new Callback<TableView<String>, TableRow<String>>() {
2965             @Override public TableRow<String> call(TableView<String> param) {
2966                 rt36556_instanceCount++;
2967                 return new TableRow<>();
2968             }
2969         });
2970 
2971         TableColumn<String, String> tableColumn = new TableColumn<>();
2972         tableColumn.setCellValueFactory(rowValue -> new SimpleStringProperty(rowValue.getValue()));
2973 
2974         tableView.getColumns().add(tableColumn);
2975 
2976         for (int i = 0; i < 1000; i++) {
2977             tableView.getItems().add("Row " + i);
2978         }
2979 
2980         StackPane root = new StackPane();
2981         root.getChildren().add(tableView);
2982 
2983         StageLoader sl = new StageLoader(root);
2984 
2985         final int cellCountAtStart = rt36556_instanceCount;
2986 
2987         // start scrolling
2988         for (int i = 0; i < 1000; i++) {
2989             tableView.scrollTo(i);
2990 //            Toolkit.getToolkit().firePulse();
2991         }
2992 
2993         assertEquals(cellCountAtStart, rt36556_instanceCount);
2994         sl.dispose();
2995     }
2996 
2997     @Test public void test_rt36556_mouseWheel() {
2998         rt36556_instanceCount = 0;
2999 
3000         TableView<String> tableView = new TableView<>();
3001         tableView.setRowFactory(new Callback<TableView<String>, TableRow<String>>() {
3002             @Override public TableRow<String> call(TableView<String> param) {
3003                 rt36556_instanceCount++;
3004                 return new TableRow<String>();
3005             }
3006         });
3007 
3008         TableColumn<String, String> tableColumn = new TableColumn<>();
3009         tableColumn.setCellValueFactory(rowValue -> new SimpleStringProperty(rowValue.getValue()));
3010         tableView.getColumns().add(tableColumn);
3011 
3012         for (int i = 0; i < 1000; i++) {
3013             tableView.getItems().add("Row " + i);
3014         }
3015 
3016         StackPane root = new StackPane();
3017         root.getChildren().add(tableView);
3018 
3019         StageLoader sl = new StageLoader(root);
3020 
3021         final int cellCountAtStart = rt36556_instanceCount;
3022 
3023         // start scrolling - we call VirtualFlow.adjustPixels, which is what
3024         // is called when the mouse wheel is scrolled
3025         VirtualFlow flow = VirtualFlowTestUtils.getVirtualFlow(tableView);
3026         flow.scrollPixels(1000 * 24);
3027 
3028         assertEquals(cellCountAtStart, rt36556_instanceCount);
3029 
3030         sl.dispose();
3031     }
3032 
3033     @Test public void test_rt_36656_removeFromSortOrder() {
3034         test_rt_36656(true, false, false);
3035     }
3036 
3037     @Test public void test_rt_36656_removeFromColumns() {
3038         test_rt_36656(false, true, false);
3039     }
3040 
3041     @Test public void test_rt_36656_setInvisible() {
3042         test_rt_36656(false, false, true);
3043     }
3044 
3045     private void test_rt_36656(boolean removeFromSortOrder, boolean removeFromColumns, boolean setInvisible) {
3046         TableView<Person> table = new TableView<Person>();
3047         table.setItems(personTestData);
3048 
3049         TableColumn firstNameCol = new TableColumn("First Name");
3050         firstNameCol.setMinWidth(100);
3051         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
3052 
3053         TableColumn lastNameCol = new TableColumn("Last Name");
3054         lastNameCol.setMinWidth(100);
3055         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
3056 
3057         TableColumn emailCol = new TableColumn("Email");
3058         emailCol.setMinWidth(200);
3059         emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
3060 
3061         table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
3062 
3063         new StageLoader(table);
3064 
3065         TableColumnHeader firstNameColHeader = VirtualFlowTestUtils.getTableColumnHeader(table, firstNameCol);
3066         TableColumnHeader lastNameColHeader = VirtualFlowTestUtils.getTableColumnHeader(table, lastNameCol);
3067         TableColumnHeader emailColHeader = VirtualFlowTestUtils.getTableColumnHeader(table, emailCol);
3068 
3069         // test initial state
3070         assertEquals(-1, TableColumnHeaderRetriever.getSortPos(firstNameColHeader));
3071         assertEquals(-1, TableColumnHeaderRetriever.getSortPos(lastNameColHeader));
3072         assertEquals(-1, TableColumnHeaderRetriever.getSortPos(emailColHeader));
3073 
3074         // set an order including all columns
3075         table.getSortOrder().addAll(firstNameCol, lastNameCol, emailCol);
3076         assertEquals(0, TableColumnHeaderRetriever.getSortPos(firstNameColHeader));
3077         assertEquals(1, TableColumnHeaderRetriever.getSortPos(lastNameColHeader));
3078         assertEquals(2, TableColumnHeaderRetriever.getSortPos(emailColHeader));
3079 
3080         if (removeFromSortOrder) {
3081             // Remove lastNameCol from the table sortOrder list, so this column
3082             // is no longer part of the sort comparator
3083             table.getSortOrder().remove(lastNameCol);
3084         } else if (removeFromColumns) {
3085             // Remove lastNameCol from the table entirely.
3086             table.getColumns().remove(lastNameCol);
3087         } else if (setInvisible) {
3088             // Hide the lastNameColumn.
3089             lastNameCol.setVisible(false);
3090         }
3091 
3092         // Regardless of action taken above, expect lastNameCol sortPos to be -1
3093         // and emailCol sortPos to shift from 2 to 1.
3094         assertEquals(0, TableColumnHeaderRetriever.getSortPos(firstNameColHeader));
3095         assertEquals(-1, TableColumnHeaderRetriever.getSortPos(lastNameColHeader));
3096         assertEquals(1, TableColumnHeaderRetriever.getSortPos(emailColHeader));
3097     }
3098 
3099     @Test public void test_rt_36670() {
3100         final ObservableList<Person> data = FXCollections.observableArrayList(
3101                         new Person("Jacob", "Smith", "jacob.smith@example.com", true),
3102                         new Person("Isabella", "Johnson", "isabella.johnson@example.com", false),
3103                         new Person("Ethan", "Williams", "ethan.williams@example.com", true),
3104                         new Person("Emma", "Jones", "emma.jones@example.com", true),
3105                         new Person("Michael", "Brown", "michael.brown@example.com", false));
3106 
3107         TableColumn invitedCol = new TableColumn<>();
3108         invitedCol.setText("Invited");
3109         invitedCol.setMinWidth(70);
3110         invitedCol.setCellValueFactory(new PropertyValueFactory("invited"));
3111         invitedCol.setCellFactory(CheckBoxTableCell.forTableColumn(invitedCol));
3112 
3113         TableColumn firstNameCol = new TableColumn();
3114         firstNameCol.setText("First");
3115         firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
3116 
3117         TableView tableView = new TableView(data);
3118         tableView.getColumns().addAll(invitedCol, firstNameCol);
3119 
3120         StageLoader sl = new StageLoader(tableView);
3121 
3122         // get the checkboxes
3123         CheckBox row0CheckBox = (CheckBox) VirtualFlowTestUtils.getCell(tableView, 0, 0).getGraphic();
3124         CheckBox row1CheckBox = (CheckBox) VirtualFlowTestUtils.getCell(tableView, 1, 0).getGraphic();
3125         CheckBox row2CheckBox = (CheckBox) VirtualFlowTestUtils.getCell(tableView, 2, 0).getGraphic();
3126         CheckBox row3CheckBox = (CheckBox) VirtualFlowTestUtils.getCell(tableView, 3, 0).getGraphic();
3127         CheckBox row4CheckBox = (CheckBox) VirtualFlowTestUtils.getCell(tableView, 4, 0).getGraphic();
3128 
3129         // check initial state of all checkboxes
3130         assertTrue(row0CheckBox.isSelected());
3131         assertFalse(row1CheckBox.isSelected());
3132         assertTrue(row2CheckBox.isSelected());
3133         assertTrue(row3CheckBox.isSelected());
3134         assertFalse(row4CheckBox.isSelected());
3135 
3136         // sort the table based on the invited column
3137         tableView.getSortOrder().add(invitedCol);
3138         Toolkit.getToolkit().firePulse();
3139 
3140         // The sort order has changed, with unselected items at the top and
3141         // selected items beneath them.
3142         assertFalse(row0CheckBox.isSelected());
3143         assertFalse(row1CheckBox.isSelected());
3144         assertTrue(row2CheckBox.isSelected());
3145         assertTrue(row3CheckBox.isSelected());
3146         assertTrue(row4CheckBox.isSelected());
3147 
3148         // now, select the 'Michael' row, which is row 1
3149         row1CheckBox.setSelected(true);
3150         Toolkit.getToolkit().firePulse();
3151 
3152         // only the Michael row should have changed state - but the bug
3153         // identified in RT-36670 shows that row 0 is also selected
3154         assertFalse(row0CheckBox.isSelected());
3155         assertTrue(row1CheckBox.isSelected());
3156         assertTrue(row2CheckBox.isSelected());
3157         assertTrue(row3CheckBox.isSelected());
3158         assertTrue(row4CheckBox.isSelected());
3159 
3160         sl.dispose();
3161     }
3162 
3163     @Test public void test_rt_36669() {
3164         TableView<Person> table = new TableView<>(personTestData);
3165 
3166         TableColumn firstNameCol = new TableColumn("First Name");
3167         firstNameCol.setMinWidth(100);
3168         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
3169 
3170         TableColumn lastNameCol = new TableColumn("Last Name");
3171         lastNameCol.setMinWidth(100);
3172         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
3173 
3174         TableColumn emailCol = new TableColumn("Email");
3175         emailCol.setMinWidth(200);
3176         emailCol.setCellValueFactory(new PropertyValueFactory<Person, String>("email"));
3177 
3178         table.getColumns().addAll(firstNameCol, lastNameCol, emailCol);
3179         table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
3180 
3181         StageLoader sl = new StageLoader(table);
3182 
3183         ScrollBar vbar = VirtualFlowTestUtils.getVirtualFlowVerticalScrollbar(table);
3184         ScrollBar hbar = VirtualFlowTestUtils.getVirtualFlowHorizontalScrollbar(table);
3185 
3186         // firstly test case where the TableView is tall enough to not need a vbar
3187         assertFalse(vbar.isVisible());
3188         assertFalse(hbar.isVisible());
3189 
3190         // now make the table quite narrow and ensure that even if a vbar appears
3191         // that the hbar does not appear
3192         table.setMaxHeight(30);
3193         Toolkit.getToolkit().firePulse();
3194         assertTrue(vbar.isVisible());
3195         assertFalse(hbar.isVisible());
3196 
3197         sl.dispose();
3198     }
3199 
3200     private int rt_37061_index_counter = 0;
3201     private int rt_37061_item_counter = 0;
3202     @Test public void test_rt_37061() {
3203         TableView<Integer> tv = new TableView<>();
3204         tv.getItems().add(1);
3205         tv.getSelectionModel().select(0);
3206 
3207         // note we add the listeners after the selection is made, so the counters
3208         // at this point are still both at zero.
3209         tv.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> {
3210             rt_37061_index_counter++;
3211         });
3212 
3213         tv.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
3214             rt_37061_item_counter++;
3215         });
3216 
3217         // add a new item. This does not impact the selected index or selected item
3218         // so the counters should remain at zero.
3219         tv.getItems().add(2);
3220         assertEquals(0, rt_37061_index_counter);
3221         assertEquals(0, rt_37061_item_counter);
3222     }
3223 
3224     @Test public void test_rt_37058_noContent() {
3225         test_rt_37058(false);
3226     }
3227 
3228     @Test public void test_rt_37058_withContent() {
3229         test_rt_37058(true);
3230     }
3231 
3232     private void test_rt_37058(boolean hasContent) {
3233         // create table with a bunch of column and no rows...
3234         TableView<Integer> table = new TableView<>();
3235         TableColumn<Integer, Integer> column = new TableColumn<>("Column");
3236         table.getColumns().add(column);
3237         column.setPrefWidth(150);
3238 
3239         if (hasContent) {
3240             table.getItems().add(1);
3241         }
3242 
3243         StageLoader sl = new StageLoader(table);
3244         Toolkit.getToolkit().firePulse();
3245 
3246         assertEquals(150, column.getWidth(), 0.0);
3247 
3248         sl.dispose();
3249     }
3250 
3251     @Test public void test_rt_37057_test1_MoveColumn() {
3252         // create table with a bunch of column and no rows...
3253         TableView<Integer> table = new TableView<>();
3254         for ( int i = 1; i <= 10; i++ ) {
3255             TableColumn<Integer, Integer> column = new TableColumn<>("" + i);
3256             table.getColumns().add( column );
3257 
3258             // sneak some hidden columns in there
3259             column = new TableColumn<>("h" + i);
3260             column.setVisible( false );
3261             table.getColumns().add( column );
3262         }
3263 
3264         StageLoader sl = new StageLoader(table);
3265 
3266         TableColumn column1 = table.getVisibleLeafColumn(0);
3267         TableColumn column2 = table.getVisibleLeafColumn(1);
3268 
3269         // get the headers of a few columns
3270         TableColumnHeader header1 = VirtualFlowTestUtils.getTableColumnHeader(table, column1);
3271         TableColumnHeader header2 = VirtualFlowTestUtils.getTableColumnHeader(table, column2);
3272 
3273         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3274         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3275 
3276         // move as per first instructions in RT-37057. Note that the moveColumn
3277         // positions seem counter-intuitive. I got these numbers by printing
3278         // the positions when in a manual test run (using the test script in
3279         // RT-37057).
3280 
3281         // Drag column 1 to slot 1. As expected, the column position doesn't change.
3282         TableColumnHeaderRetriever.moveColumn(column1, 0);
3283         assertEquals(column1, table.getVisibleLeafColumn(0));
3284         assertEquals(column2, table.getVisibleLeafColumn(1));
3285         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3286         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3287 
3288         // Drag column 1 to slot 2. As expected, the column 1 and 2 swap positions.
3289         TableColumnHeaderRetriever.moveColumn(column1, 1);
3290         assertEquals(column2, table.getVisibleLeafColumn(0));
3291         assertEquals(column1, table.getVisibleLeafColumn(1));
3292         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header1));
3293         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header2));
3294 
3295         // Drag column 1 to slot 0. As expected, the column 1 and 2 swap positions.
3296         TableColumnHeaderRetriever.moveColumn(column1, 0);
3297         assertEquals(column1, table.getVisibleLeafColumn(0));
3298         assertEquals(column2, table.getVisibleLeafColumn(1));
3299         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3300         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3301 
3302         // Drag column 1 to slot 1 again. What? Why did they swap positions this time?
3303         TableColumnHeaderRetriever.moveColumn(column1, 0);
3304         assertEquals(column1, table.getVisibleLeafColumn(0));
3305         assertEquals(column2, table.getVisibleLeafColumn(1));
3306         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3307         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3308 
3309         sl.dispose();
3310     }
3311 
3312     @Test public void test_rt_37057_test1_MouseEvents() {
3313         // create table with a bunch of column and no rows...
3314         TableView<Integer> table = new TableView<>();
3315         for ( int i = 1; i <= 10; i++ ) {
3316             TableColumn<Integer, Integer> column = new TableColumn<>("" + i);
3317             table.getColumns().add( column );
3318 
3319             // sneak some hidden columns in there
3320             column = new TableColumn<>("h" + i);
3321             column.setVisible( false );
3322             column.impl_setWidth(50);
3323             column.setResizable(false);
3324             table.getColumns().add( column );
3325         }
3326 
3327         StageLoader sl = new StageLoader(table);
3328 
3329         TableColumn column1 = table.getVisibleLeafColumn(0);
3330         TableColumn column2 = table.getVisibleLeafColumn(1);
3331 
3332         // get the headers of a few columns
3333         TableColumnHeader header1 = VirtualFlowTestUtils.getTableColumnHeader(table, column1);
3334         TableColumnHeader header2 = VirtualFlowTestUtils.getTableColumnHeader(table, column2);
3335 
3336         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3337         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3338 
3339         // move as per first instructions in RT-37057. The dragOffset and sceneX
3340         // values passed into moveColumn have been derived from a manual run of
3341         // the test application attached to RT-37057 with debug output printed
3342         // in TableColumnHeader
3343 
3344         // Drag column 1 to slot 1. As expected, the column position doesn't change.
3345         TableColumnHeaderRetriever.moveColumn(column1, 9, 61);
3346         assertEquals(column1, table.getVisibleLeafColumn(0));
3347         assertEquals(column2, table.getVisibleLeafColumn(1));
3348         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3349         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3350 
3351         // Drag column 1 to slot 2. As expected, the column 1 and 2 swap positions.
3352         TableColumnHeaderRetriever.moveColumn(column1, 12, 139);
3353         assertEquals(column2, table.getVisibleLeafColumn(0));
3354         assertEquals(column1, table.getVisibleLeafColumn(1));
3355         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header1));
3356         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header2));
3357 
3358         // Drag column 1 to slot 0. As expected, the column 1 and 2 swap positions.
3359         TableColumnHeaderRetriever.moveColumn(column1, 45, 21);
3360         assertEquals(column1, table.getVisibleLeafColumn(0));
3361         assertEquals(column2, table.getVisibleLeafColumn(1));
3362         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3363         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3364 
3365         // Drag column 1 to slot 1 again. What? Why did they swap positions this time?
3366         TableColumnHeaderRetriever.moveColumn(column1, 19, 63);
3367         assertEquals(column1, table.getVisibleLeafColumn(0));
3368         assertEquals(column2, table.getVisibleLeafColumn(1));
3369         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3370         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3371 
3372         sl.dispose();
3373     }
3374 
3375     @Test public void test_rt_37057_test2_MoveColumn() {
3376         // create table with a bunch of column and no rows...
3377         TableView<Integer> table = new TableView<>();
3378         for ( int i = 1; i <= 10; i++ ) {
3379             TableColumn<Integer, Integer> column = new TableColumn<>("" + i);
3380             table.getColumns().add( column );
3381 
3382             // sneak some hidden columns in there
3383             column = new TableColumn<>("h" + i);
3384             column.setVisible( false );
3385             table.getColumns().add( column );
3386         }
3387 
3388         StageLoader sl = new StageLoader(table);
3389 
3390         TableColumn column1 = table.getVisibleLeafColumn(0);
3391         TableColumn column2 = table.getVisibleLeafColumn(1);
3392         TableColumn column4 = table.getVisibleLeafColumn(3);
3393 
3394         // get the headers of a few columns
3395         TableColumnHeader header1 = VirtualFlowTestUtils.getTableColumnHeader(table, column1);
3396         TableColumnHeader header2 = VirtualFlowTestUtils.getTableColumnHeader(table, column2);
3397         TableColumnHeader header4 = VirtualFlowTestUtils.getTableColumnHeader(table, column4);
3398 
3399         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3400         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3401 
3402         // move as per second instructions in RT-37057. Note that the moveColumn
3403         // positions seem counter-intuitive. I got these numbers by printing
3404         // the positions when in a manual test run (using the test script in
3405         // RT-37057).
3406 
3407         // Drag column 1 to slot 2. As expected, the column 1 and 2 swap positions
3408         TableColumnHeaderRetriever.moveColumn(column1, 1);
3409         assertEquals(column2, table.getVisibleLeafColumn(0));
3410         assertEquals(column1, table.getVisibleLeafColumn(1));
3411         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header1));
3412         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header2));
3413 
3414         // Drag column 1 to slot 0. As expected, the column 1 and 2 swap positions.
3415         TableColumnHeaderRetriever.moveColumn(column1, 0);
3416         assertEquals(column1, table.getVisibleLeafColumn(0));
3417         assertEquals(column2, table.getVisibleLeafColumn(1));
3418         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3419         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3420 
3421         // Drag column 4 to slot 1. What? It behaves like it was dragged to slot 0?!
3422         TableColumnHeaderRetriever.moveColumn(column4, 1);
3423         assertEquals(column1, table.getVisibleLeafColumn(0));
3424         assertEquals(column4, table.getVisibleLeafColumn(1));
3425         assertEquals(column2, table.getVisibleLeafColumn(2));
3426         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3427         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header4));
3428         assertEquals(2, TableColumnHeaderRetriever.getColumnIndex(header2));
3429 
3430         sl.dispose();
3431     }
3432 
3433     @Test public void test_rt_37057_test2_MouseEvents() {
3434         // create table with a bunch of column and no rows...
3435         TableView<Integer> table = new TableView<>();
3436         for ( int i = 1; i <= 10; i++ ) {
3437             TableColumn<Integer, Integer> column = new TableColumn<>("" + i);
3438             table.getColumns().add( column );
3439 
3440             // sneak some hidden columns in there
3441             column = new TableColumn<>("h" + i);
3442             column.setVisible( false );
3443             column.impl_setWidth(50);
3444             column.setResizable(false);
3445             table.getColumns().add( column );
3446         }
3447 
3448         StageLoader sl = new StageLoader(table);
3449 
3450         TableColumn column1 = table.getVisibleLeafColumn(0);
3451         TableColumn column2 = table.getVisibleLeafColumn(1);
3452         TableColumn column4 = table.getVisibleLeafColumn(3);
3453 
3454         // get the headers of a few columns
3455         TableColumnHeader header1 = VirtualFlowTestUtils.getTableColumnHeader(table, column1);
3456         TableColumnHeader header2 = VirtualFlowTestUtils.getTableColumnHeader(table, column2);
3457         TableColumnHeader header4 = VirtualFlowTestUtils.getTableColumnHeader(table, column4);
3458 
3459         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3460         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3461 
3462         // move as per second instructions in RT-37057. The dragOffset and sceneX
3463         // values passed into moveColumn have been derived from a manual run of
3464         // the test application attached to RT-37057 with debug output printed
3465         // in TableColumnHeader
3466 
3467         // Drag column 1 to slot 2. As expected, the column 1 and 2 swap positions
3468         TableColumnHeaderRetriever.moveColumn(column1, 25, 136);
3469         assertEquals(column2, table.getVisibleLeafColumn(0));
3470         assertEquals(column1, table.getVisibleLeafColumn(1));
3471         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header1));
3472         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header2));
3473 
3474         // Drag column 1 to slot 0. As expected, the column 1 and 2 swap positions.
3475         TableColumnHeaderRetriever.moveColumn(column1, 51, 23);
3476         assertEquals(column1, table.getVisibleLeafColumn(0));
3477         assertEquals(column2, table.getVisibleLeafColumn(1));
3478         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3479         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header2));
3480 
3481         // Drag column 4 to slot 1. What? It behaves like it was dragged to slot 0?!
3482         TableColumnHeaderRetriever.moveColumn(column4, 56, 103);
3483         assertEquals(column1, table.getVisibleLeafColumn(0));
3484         assertEquals(column4, table.getVisibleLeafColumn(1));
3485         assertEquals(column2, table.getVisibleLeafColumn(2));
3486         assertEquals(0, TableColumnHeaderRetriever.getColumnIndex(header1));
3487         assertEquals(1, TableColumnHeaderRetriever.getColumnIndex(header4));
3488         assertEquals(2, TableColumnHeaderRetriever.getColumnIndex(header2));
3489 
3490         sl.dispose();
3491     }
3492 
3493     @Test public void test_rt_37054_noScroll() {
3494         test_rt_37054(false);
3495     }
3496 
3497     @Test public void test_rt_37054_scroll() {
3498         test_rt_37054(true);
3499     }
3500 
3501     private void test_rt_37054(boolean scroll) {
3502         ObjectProperty<Integer> offset = new SimpleObjectProperty<Integer>(0);
3503 
3504         // create table with a bunch of rows and 1 column...
3505         TableView<Integer> table = new TableView<>();
3506         for ( int i = 1; i <= 50; i++ ) {
3507             table.getItems().add(i);
3508         }
3509         final TableColumn<Integer, Integer> column = new TableColumn<>("Column");
3510         table.getColumns().add( column );
3511         column.setPrefWidth( 150 );
3512 
3513         // each cell displays x, where x = "cell row number + offset"
3514         column.setCellValueFactory( cdf -> new ObjectBinding<Integer>() {
3515             { super.bind( offset ); }
3516 
3517             @Override protected Integer computeValue() {
3518                 return cdf.getValue() + offset.get();
3519             }
3520         });
3521 
3522         StackPane root = new StackPane();
3523         root.getChildren().add( table );
3524 
3525         StageLoader sl = new StageLoader(root);
3526 
3527         int index = scroll ? 0 : 25;
3528 
3529         if (scroll) {
3530             // we scroll to force the table cells to update the objects they observe
3531             table.scrollTo(index);
3532             Toolkit.getToolkit().firePulse();
3533         }
3534 
3535         TableCell cell = (TableCell) VirtualFlowTestUtils.getCell(table, index + 3, 0);
3536         final int initialValue = (Integer) cell.getItem();
3537 
3538         // increment the offset value
3539         offset.setValue(offset.get() + 1);
3540         Toolkit.getToolkit().firePulse();
3541 
3542         final int incrementedValue = (Integer) cell.getItem();
3543         assertEquals(initialValue + 1, incrementedValue);
3544 
3545         sl.dispose();
3546     }
3547 
3548     @Test public void test_rt_37429() {
3549         // get the current exception handler before replacing with our own,
3550         // as ListListenerHelp intercepts the exception otherwise
3551         final Thread.UncaughtExceptionHandler exceptionHandler = Thread.currentThread().getUncaughtExceptionHandler();
3552         Thread.currentThread().setUncaughtExceptionHandler((t, e) -> fail("We don't expect any exceptions in this test!"));
3553 
3554         // table columns - 1 column; name
3555         TableColumn<String, String> nameColumn = new TableColumn<>("name");
3556         nameColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue()));
3557         nameColumn.setPrefWidth(200);
3558 
3559         // table
3560         TableView<String> table = new TableView<>();
3561         table.setItems(FXCollections.observableArrayList("one", "two", "three", "four", "five"));
3562         table.getColumns().addAll(nameColumn);
3563 
3564         table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<String>) c -> {
3565             while (c.next()) {
3566                 if(c.wasRemoved()) {
3567                     // The removed list of items must be iterated or the AIOOBE will
3568                     // not be thrown when getAddedSubList is called.
3569                     c.getRemoved().forEach(item -> {});
3570                 }
3571 
3572                 if (c.wasAdded()) {
3573                     c.getAddedSubList();
3574                 }
3575             }
3576         });
3577 
3578         StageLoader sl = new StageLoader(table);
3579 
3580         table.getSelectionModel().select(0);
3581         table.getSortOrder().add(nameColumn);
3582 
3583         sl.dispose();
3584 
3585         // reset the exception handler
3586         Thread.currentThread().setUncaughtExceptionHandler(exceptionHandler);
3587     }
3588 
3589     private int rt_37429_items_change_count = 0;
3590     private int rt_37429_cells_change_count = 0;
3591     @Test public void test_rt_37429_sortEventsShouldNotFireExtraChangeEvents() {
3592         // table columns - 1 column; name
3593         TableColumn<String, String> nameColumn = new TableColumn<>("name");
3594         nameColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue()));
3595         nameColumn.setPrefWidth(200);
3596 
3597         // table
3598         TableView<String> table = new TableView<>();
3599         table.setItems(FXCollections.observableArrayList("a", "c", "b"));
3600         table.getColumns().addAll(nameColumn);
3601 
3602         table.getSelectionModel().getSelectedItems().addListener((ListChangeListener<String>) c -> {
3603             while (c.next()) {
3604                 rt_37429_items_change_count++;
3605             }
3606         });
3607         table.getSelectionModel().getSelectedCells().addListener((ListChangeListener<TablePosition>) c -> {
3608             while (c.next()) {
3609                 rt_37429_cells_change_count++;
3610             }
3611         });
3612 
3613         StageLoader sl = new StageLoader(table);
3614 
3615         assertEquals(0, rt_37429_items_change_count);
3616         assertEquals(0, rt_37429_cells_change_count);
3617 
3618         table.getSelectionModel().select(0);
3619         assertEquals(1, rt_37429_items_change_count);
3620         assertEquals(1, rt_37429_cells_change_count);
3621 
3622         table.getSortOrder().add(nameColumn);
3623         assertEquals(1, rt_37429_items_change_count);
3624         assertEquals(1, rt_37429_cells_change_count);
3625 
3626         nameColumn.setSortType(TableColumn.SortType.DESCENDING);
3627         assertEquals(1, rt_37429_items_change_count);
3628         assertEquals(2, rt_37429_cells_change_count);
3629 
3630         nameColumn.setSortType(TableColumn.SortType.ASCENDING);
3631         assertEquals(1, rt_37429_items_change_count);
3632         assertEquals(3, rt_37429_cells_change_count);
3633 
3634         sl.dispose();
3635     }
3636 
3637     private int rt_37538_count = 0;
3638     @Test public void test_rt_37538_noCNextCall() {
3639         test_rt_37538(false, false);
3640     }
3641 
3642     @Test public void test_rt_37538_callCNextOnce() {
3643         test_rt_37538(true, false);
3644     }
3645 
3646     @Test public void test_rt_37538_callCNextInLoop() {
3647         test_rt_37538(false, true);
3648     }
3649 
3650     private void test_rt_37538(boolean callCNextOnce, boolean callCNextInLoop) {
3651         TableView<Integer> table = new TableView<>();
3652         for ( int i = 1; i <= 50; i++ ) {
3653             table.getItems().add(i);
3654         }
3655         final TableColumn<Integer, Integer> column = new TableColumn<>("Column");
3656         table.getColumns().add(column);
3657         column.setCellValueFactory( cdf -> new ReadOnlyObjectWrapper<Integer>(cdf.getValue()));
3658 
3659         table.getSelectionModel().getSelectedItems().addListener((ListChangeListener.Change<? extends Integer> c) -> {
3660             if (callCNextOnce) {
3661                 c.next();
3662             } else if (callCNextInLoop) {
3663                 while (c.next()) {
3664                     // no-op
3665                 }
3666             }
3667 
3668             if (rt_37538_count >= 1) {
3669                 Thread.dumpStack();
3670                 fail("This method should only be called once");
3671             }
3672 
3673             rt_37538_count++;
3674         });
3675 
3676         StageLoader sl = new StageLoader(table);
3677         assertEquals(0, rt_37538_count);
3678         table.getSelectionModel().select(0);
3679         assertEquals(1, rt_37538_count);
3680         sl.dispose();
3681     }
3682 
3683     @Ignore("Fix not yet developed for TableView")
3684     @Test public void test_rt_35395_testCell_fixedCellSize() {
3685         test_rt_35395(true, true);
3686     }
3687 
3688     @Ignore("Fix not yet developed for TableView")
3689     @Test public void test_rt_35395_testCell_notFixedCellSize() {
3690         test_rt_35395(true, false);
3691     }
3692 
3693     @Ignore("Fix not yet developed for TableView")
3694     @Test public void test_rt_35395_testRow_fixedCellSize() {
3695         test_rt_35395(false, true);
3696     }
3697 
3698     @Ignore("Fix not yet developed for TableView")
3699     @Test public void test_rt_35395_testRow_notFixedCellSize() {
3700         test_rt_35395(false, false);
3701     }
3702 
3703     private int rt_35395_counter;
3704     private void test_rt_35395(boolean testCell, boolean useFixedCellSize) {
3705         rt_35395_counter = 0;
3706 
3707         ObservableList<String> items = FXCollections.observableArrayList();
3708         for (int i = 0; i < 20; ++i) {
3709             items.addAll("red", "green", "blue", "purple");
3710         }
3711 
3712         TableView<String> tableView = new TableView<>(items);
3713         if (useFixedCellSize) {
3714             tableView.setFixedCellSize(24);
3715         }
3716         tableView.setRowFactory(tv -> new TableRow<String>() {
3717             @Override protected void updateItem(String color, boolean empty) {
3718                 rt_35395_counter += testCell ? 0 : 1;
3719                 super.updateItem(color, empty);
3720             }
3721         });
3722 
3723         TableColumn<String,String> column = new TableColumn<>("Column");
3724         column.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue()));
3725         column.setCellFactory(tv -> new TableCell<String,String>() {
3726             @Override protected void updateItem(String color, boolean empty) {
3727                 rt_35395_counter += testCell ? 1 : 0;
3728                 super.updateItem(color, empty);
3729                 setText(null);
3730                 if (empty) {
3731                     setGraphic(null);
3732                 } else {
3733                     Rectangle rect = new Rectangle(16, 16);
3734                     rect.setStyle("-fx-fill: " + color);
3735                     setGraphic(rect);
3736                 }
3737             }
3738         });
3739         tableView.getColumns().addAll(column);
3740 
3741         StageLoader sl = new StageLoader(tableView);
3742 
3743         Platform.runLater(() -> {
3744             rt_35395_counter = 0;
3745             items.set(10, "yellow");
3746             Platform.runLater(() -> {
3747                 Toolkit.getToolkit().firePulse();
3748                 assertEquals(1, rt_35395_counter);
3749                 rt_35395_counter = 0;
3750                 items.set(30, "yellow");
3751                 Platform.runLater(() -> {
3752                     Toolkit.getToolkit().firePulse();
3753                     assertEquals(0, rt_35395_counter);
3754                     rt_35395_counter = 0;
3755                     tableView.scrollTo(5);
3756                     Platform.runLater(() -> {
3757                         Toolkit.getToolkit().firePulse();
3758                         assertEquals(5, rt_35395_counter);
3759                         rt_35395_counter = 0;
3760                         tableView.scrollTo(55);
3761                         Platform.runLater(() -> {
3762                             Toolkit.getToolkit().firePulse();
3763 
3764                             int expected = useFixedCellSize ? 17 : 53;
3765                             assertEquals(expected, rt_35395_counter);
3766                             sl.dispose();
3767                         });
3768                     });
3769                 });
3770             });
3771         });
3772     }
3773 
3774     @Test public void test_rt_37632() {
3775         final ObservableList<String> listOne = FXCollections.observableArrayList("A", "B", "C");
3776         final ObservableList<String> listTwo = FXCollections.observableArrayList("C");
3777 
3778         TableColumn<String,String> tableColumn = new TableColumn("column");
3779         tableColumn.setCellValueFactory(c -> new ReadOnlyStringWrapper(c.getValue()));
3780 
3781         final TableView<String> tableView = new TableView<>();
3782         tableView.getColumns().add(tableColumn);
3783         MultipleSelectionModel<String> sm = tableView.getSelectionModel();
3784         tableView.setItems(listOne);
3785         tableView.getSelectionModel().selectFirst();
3786 
3787         assertEquals(0, sm.getSelectedIndex());
3788         assertEquals("A", sm.getSelectedItem());
3789         assertEquals(1, sm.getSelectedIndices().size());
3790         assertEquals(0, (int) sm.getSelectedIndices().get(0));
3791         assertEquals(1, sm.getSelectedItems().size());
3792         assertEquals("A", sm.getSelectedItems().get(0));
3793 
3794         tableView.setItems(listTwo);
3795 
3796         assertEquals(-1, sm.getSelectedIndex());
3797         assertNull(sm.getSelectedItem());
3798         assertEquals(0, sm.getSelectedIndices().size());
3799         assertEquals(0, sm.getSelectedItems().size());
3800     }
3801 
3802     @Test public void test_rt_38464_rowSelection_selectFirstRowOnly() {
3803         TableColumn firstNameCol = new TableColumn("First");
3804         firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
3805 
3806         TableColumn lastNameCol = new TableColumn("Last");
3807         lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
3808 
3809         TableView tableView = new TableView(personTestData);
3810         tableView.getColumns().addAll(firstNameCol, lastNameCol);
3811 
3812         TableView.TableViewSelectionModel<Person> sm = tableView.getSelectionModel();
3813         sm.setCellSelectionEnabled(false);
3814         sm.setSelectionMode(SelectionMode.MULTIPLE);
3815 
3816         sm.select(0);
3817 
3818         assertTrue(sm.isSelected(0));
3819         assertTrue(sm.isSelected(0, firstNameCol));
3820         assertTrue(sm.isSelected(0, lastNameCol));
3821 
3822         assertEquals(1, sm.getSelectedIndices().size());
3823         assertEquals(1, sm.getSelectedItems().size());
3824         assertEquals(1, sm.getSelectedCells().size());
3825     }
3826 
3827     @Test public void test_rt_38464_rowSelection_selectFirstRowAndThenCallNoOpMethods() {
3828         TableColumn firstNameCol = new TableColumn("First");
3829         firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
3830 
3831         TableColumn lastNameCol = new TableColumn("Last");
3832         lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
3833 
3834         TableView tableView = new TableView(personTestData);
3835         tableView.getColumns().addAll(firstNameCol, lastNameCol);
3836 
3837         TableView.TableViewSelectionModel<Person> sm = tableView.getSelectionModel();
3838         sm.setCellSelectionEnabled(false);
3839         sm.setSelectionMode(SelectionMode.MULTIPLE);
3840 
3841         sm.select(0);               // select first row
3842         sm.select(0);               // this should be a no-op
3843         sm.select(0, firstNameCol); // so should this, as we are in row selection mode
3844         sm.select(0, lastNameCol);  // and same here
3845 
3846         assertTrue(sm.isSelected(0));
3847         assertTrue(sm.isSelected(0, firstNameCol));
3848         assertTrue(sm.isSelected(0, lastNameCol));
3849 
3850         assertEquals(1, sm.getSelectedIndices().size());
3851         assertEquals(1, sm.getSelectedItems().size());
3852         assertEquals(1, sm.getSelectedCells().size());
3853     }
3854 
3855 
3856     @Test public void test_rt_38464_cellSelection_selectFirstRowOnly() {
3857         TableColumn firstNameCol = new TableColumn("First");
3858         firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
3859 
3860         TableColumn lastNameCol = new TableColumn("Last");
3861         lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
3862 
3863         TableView tableView = new TableView(personTestData);
3864         tableView.getColumns().addAll(firstNameCol, lastNameCol);
3865 
3866         TableView.TableViewSelectionModel<Person> sm = tableView.getSelectionModel();
3867         sm.setCellSelectionEnabled(true);
3868         sm.setSelectionMode(SelectionMode.MULTIPLE);
3869 
3870         // select first row. This should be translated into selection of all
3871         // cells in this row, but does not result in the row itself being
3872         // considered selected.
3873         sm.select(0);
3874 
3875         assertFalse(sm.isSelected(0));
3876         assertTrue(sm.isSelected(0, firstNameCol));
3877         assertTrue(sm.isSelected(0, lastNameCol));
3878 
3879         assertEquals(1, sm.getSelectedIndices().size());
3880         assertEquals(1, sm.getSelectedItems().size());
3881         assertEquals(2, sm.getSelectedCells().size());
3882     }
3883 
3884     @Test public void test_rt_38464_cellSelection_selectFirstRowAndThenCallNoOpMethods() {
3885         TableColumn firstNameCol = new TableColumn("First");
3886         firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
3887 
3888         TableColumn lastNameCol = new TableColumn("Last");
3889         lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
3890 
3891         TableView tableView = new TableView(personTestData);
3892         tableView.getColumns().addAll(firstNameCol, lastNameCol);
3893 
3894         TableView.TableViewSelectionModel<Person> sm = tableView.getSelectionModel();
3895         sm.setCellSelectionEnabled(true);
3896         sm.setSelectionMode(SelectionMode.MULTIPLE);
3897 
3898         // select first row. This should be translated into selection of all
3899         // cells in this row, but does not result in the row itself being
3900         // considered selected.
3901         sm.select(0);               // select first row
3902         sm.select(0, firstNameCol); // This line and the next should be no-ops
3903         sm.select(0, lastNameCol);
3904 
3905         assertFalse(sm.isSelected(0));
3906         assertTrue(sm.isSelected(0, firstNameCol));
3907         assertTrue(sm.isSelected(0, lastNameCol));
3908 
3909         assertEquals(1, sm.getSelectedIndices().size());
3910         assertEquals(1, sm.getSelectedItems().size());
3911         assertEquals(2, sm.getSelectedCells().size());
3912     }
3913 
3914     @Test public void test_rt38464_selectCellMultipleTimes() {
3915         TableColumn firstNameCol = new TableColumn("First");
3916         firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
3917 
3918         TableColumn lastNameCol = new TableColumn("Last");
3919         lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
3920 
3921         TableView tableView = new TableView(personTestData);
3922         tableView.getColumns().addAll(firstNameCol, lastNameCol);
3923 
3924         TableView.TableViewSelectionModel<Person> sm = tableView.getSelectionModel();
3925         sm.setCellSelectionEnabled(true);
3926         sm.setSelectionMode(SelectionMode.MULTIPLE);
3927 
3928         // default selection when in cell selection mode
3929         assertEquals(0, sm.getSelectedCells().size());
3930         assertEquals(0, sm.getSelectedItems().size());
3931         assertEquals(0, sm.getSelectedIndices().size());
3932 
3933         // select the first cell
3934         sm.select(0, firstNameCol);
3935         assertEquals(1, sm.getSelectedCells().size());
3936         assertEquals(1, sm.getSelectedItems().size());
3937         assertEquals(1, sm.getSelectedIndices().size());
3938 
3939         // select the first cell....again
3940         sm.select(0, firstNameCol);
3941         assertEquals(1, sm.getSelectedCells().size());
3942         assertEquals(1, sm.getSelectedItems().size());
3943         assertEquals(1, sm.getSelectedIndices().size());
3944     }
3945 
3946     @Test public void test_rt38464_selectCellThenRow() {
3947         TableColumn firstNameCol = new TableColumn("First");
3948         firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
3949 
3950         TableColumn lastNameCol = new TableColumn("Last");
3951         lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
3952 
3953         TableView tableView = new TableView(personTestData);
3954         tableView.getColumns().addAll(firstNameCol, lastNameCol);
3955 
3956         TableView.TableViewSelectionModel<Person> sm = tableView.getSelectionModel();
3957         sm.setCellSelectionEnabled(true);
3958         sm.setSelectionMode(SelectionMode.MULTIPLE);
3959 
3960         // default selection when in cell selection mode
3961         assertEquals(0, sm.getSelectedCells().size());
3962         assertEquals(0, sm.getSelectedItems().size());
3963         assertEquals(0, sm.getSelectedIndices().size());
3964 
3965         // select the first cell
3966         sm.select(0, firstNameCol);
3967         assertEquals(1, sm.getSelectedCells().size());
3968         assertEquals(1, sm.getSelectedItems().size());
3969         assertEquals(1, sm.getSelectedIndices().size());
3970 
3971         // select the first row
3972         sm.select(0);
3973 
3974         // we go to 2 here as all cells in the row become selected. What we do
3975         // not expect is to go to 3, as that would mean duplication
3976         assertEquals(2, sm.getSelectedCells().size());
3977         assertEquals(1, sm.getSelectedItems().size());
3978         assertEquals(1, sm.getSelectedIndices().size());
3979     }
3980 
3981     @Test public void test_rt38464_selectRowThenCell() {
3982         TableColumn firstNameCol = new TableColumn("First");
3983         firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
3984 
3985         TableColumn lastNameCol = new TableColumn("Last");
3986         lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
3987 
3988         TableView tableView = new TableView(personTestData);
3989         tableView.getColumns().addAll(firstNameCol, lastNameCol);
3990 
3991         TableView.TableViewSelectionModel<Person> sm = tableView.getSelectionModel();
3992         sm.setCellSelectionEnabled(true);
3993         sm.setSelectionMode(SelectionMode.MULTIPLE);
3994 
3995         // default selection when in cell selection mode
3996         assertEquals(0, sm.getSelectedCells().size());
3997         assertEquals(0, sm.getSelectedItems().size());
3998         assertEquals(0, sm.getSelectedIndices().size());
3999 
4000         // select the first row
4001         sm.select(0);
4002 
4003         // we go to 2 here as all cells in the row become selected.
4004         assertEquals(2, sm.getSelectedCells().size());
4005         assertEquals(1, sm.getSelectedItems().size());
4006         assertEquals(1, sm.getSelectedIndices().size());
4007 
4008         // select the first cell - no change is expected
4009         sm.select(0, firstNameCol);
4010         assertEquals(2, sm.getSelectedCells().size());
4011         assertEquals(1, sm.getSelectedItems().size());
4012         assertEquals(1, sm.getSelectedIndices().size());
4013     }
4014 
4015     @Test public void test_rt38464_selectTests_cellSelection_singleSelection_selectsOneRow() {
4016         test_rt38464_selectTests(true, true, true);
4017     }
4018 
4019     @Test public void test_rt38464_selectTests_cellSelection_singleSelection_selectsTwoRows() {
4020         test_rt38464_selectTests(true, true, false);
4021     }
4022 
4023     @Test public void test_rt38464_selectTests_cellSelection_multipleSelection_selectsOneRow() {
4024         test_rt38464_selectTests(true, false, true);
4025     }
4026 
4027     @Test public void test_rt38464_selectTests_cellSelection_multipleSelection_selectsTwoRows() {
4028         test_rt38464_selectTests(true, false, false);
4029     }
4030 
4031     @Test public void test_rt38464_selectTests_rowSelection_singleSelection_selectsOneRow() {
4032         test_rt38464_selectTests(false, true, true);
4033     }
4034 
4035     @Test public void test_rt38464_selectTests_rowSelection_singleSelection_selectsTwoRows() {
4036         test_rt38464_selectTests(false, true, false);
4037     }
4038 
4039     @Test public void test_rt38464_selectTests_rowSelection_multipleSelection_selectsOneRow() {
4040         test_rt38464_selectTests(false, false, true);
4041     }
4042 
4043     @Test public void test_rt38464_selectTests_rowSelection_multipleSelection_selectsTwoRows() {
4044         test_rt38464_selectTests(false, false, false);
4045     }
4046 
4047     private void test_rt38464_selectTests(boolean cellSelection, boolean singleSelection, boolean selectsOneRow) {
4048         TableColumn firstNameCol = new TableColumn("First");
4049         firstNameCol.setCellValueFactory(new PropertyValueFactory("firstName"));
4050 
4051         TableColumn lastNameCol = new TableColumn("Last");
4052         lastNameCol.setCellValueFactory(new PropertyValueFactory("lastName"));
4053 
4054         TableView tableView = new TableView(personTestData);
4055         tableView.getColumns().addAll(firstNameCol, lastNameCol);
4056 
4057         TableView.TableViewSelectionModel<Person> sm = tableView.getSelectionModel();
4058         sm.setCellSelectionEnabled(cellSelection);
4059         sm.setSelectionMode(singleSelection ? SelectionMode.SINGLE : SelectionMode.MULTIPLE);
4060 
4061         // default selection when in cell selection mode
4062         assertEquals(0, sm.getSelectedCells().size());
4063         assertEquals(0, sm.getSelectedItems().size());
4064         assertEquals(0, sm.getSelectedIndices().size());
4065 
4066         if (selectsOneRow) {
4067             sm.select(0);
4068         } else {
4069             // select the first two rows
4070             sm.selectIndices(0, 1);
4071         }
4072 
4073         final int expectedCells = singleSelection                    ? 1 :
4074                                   selectsOneRow   && cellSelection   ? 2 :
4075                                   selectsOneRow   && !cellSelection  ? 1 :
4076                                   !selectsOneRow  && cellSelection   ? 4 :
4077                                /* !selectsOneRow  && !cellSelection */ 2;
4078 
4079         final int expectedItems = singleSelection ? 1 :
4080                                   selectsOneRow   ? 1 : 2;
4081 
4082         assertEquals(expectedCells, sm.getSelectedCells().size());
4083         assertEquals(expectedItems, sm.getSelectedItems().size());
4084         assertEquals(expectedItems, sm.getSelectedIndices().size());
4085 
4086         // we expect the table column of all selected cells, in this instance,
4087         // to be null as we have not explicitly stated a column, nor have we clicked
4088         // on a column. The only alternative is to use the first column.
4089         for (TablePosition<?,?> tp : sm.getSelectedCells()) {
4090             if (cellSelection) {
4091                 assertNotNull(tp.getTableColumn());
4092             } else {
4093                 assertNull(tp.getTableColumn());
4094             }
4095         }
4096     }
4097 
4098     private int rt_37853_cancelCount;
4099     private int rt_37853_commitCount;
4100     @Test public void test_rt_37853() {
4101         TableColumn<String,String> first = new TableColumn<>("first");
4102         first.setEditable(true);
4103         first.setCellFactory(TextFieldTableCell.forTableColumn());
4104         table.getColumns().add(first);
4105         table.setEditable(true);
4106 
4107         for (int i = 0; i < 10; i++) {
4108             table.getItems().add("" + i);
4109         }
4110 
4111         StageLoader sl = new StageLoader(table);
4112 
4113         first.setOnEditCancel(editEvent -> rt_37853_cancelCount++);
4114         first.setOnEditCommit(editEvent -> rt_37853_commitCount++);
4115 
4116         assertEquals(0, rt_37853_cancelCount);
4117         assertEquals(0, rt_37853_commitCount);
4118 
4119         table.edit(1, first);
4120         assertNotNull(table.getEditingCell());
4121 
4122         table.getItems().clear();
4123         assertEquals(1, rt_37853_cancelCount);
4124         assertEquals(0, rt_37853_commitCount);
4125 
4126         sl.dispose();
4127     }
4128 
4129 
4130     /**************************************************************************
4131      *
4132      * Tests (and related code) for RT-38892
4133      *
4134      *************************************************************************/
4135 
4136     private final Supplier<TableColumn<Person,String>> columnCallable = () -> {
4137         TableColumn<Person,String> column = new TableColumn<>("Last Name");
4138         column.setCellValueFactory(new PropertyValueFactory<Person,String>("lastName"));
4139         return column;
4140     };
4141 
4142     private TableColumn<Person, String> test_rt_38892_firstNameCol;
4143     private TableColumn<Person, String> test_rt_38892_lastNameCol;
4144 
4145     private TableView<Person> init_test_rt_38892() {
4146         ObservableList<Person> data = FXCollections.observableArrayList(
4147                 new Person("Jacob", "Smith", ""),
4148                 new Person("Isabella", "Johnson", ""),
4149                 new Person("Ethan", "Williams", ""),
4150                 new Person("Emma", "Jones", ""),
4151                 new Person("Michael", "Brown", "")
4152         );
4153 
4154         TableView<Person> table = new TableView<>();
4155         table.getSelectionModel().setCellSelectionEnabled(true);
4156         table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
4157         table.setItems(data);
4158 
4159         test_rt_38892_firstNameCol = new TableColumn<>("First Name");
4160         test_rt_38892_firstNameCol.setCellValueFactory(new PropertyValueFactory<>("firstName"));
4161         test_rt_38892_lastNameCol = columnCallable.get();
4162         table.getColumns().addAll(test_rt_38892_firstNameCol, test_rt_38892_lastNameCol);
4163 
4164         return table;
4165     }
4166 
4167     @Test public void test_rt_38892_focusMovesToLeftWhenPossible() {
4168         TableView<Person> table = init_test_rt_38892();
4169 
4170         TableView.TableViewFocusModel<Person> fm = table.getFocusModel();
4171         fm.focus(0, test_rt_38892_lastNameCol);
4172 
4173         // assert pre-conditions
4174         assertEquals(0, fm.getFocusedIndex());
4175         assertEquals(0, fm.getFocusedCell().getRow());
4176         assertEquals(test_rt_38892_lastNameCol, fm.getFocusedCell().getTableColumn());
4177         assertEquals(1, fm.getFocusedCell().getColumn());
4178 
4179         // now remove column where focus is and replace it with a new column.
4180         // We expect focus to move to the left one cell.
4181         table.getColumns().remove(1);
4182         table.getColumns().add(columnCallable.get());
4183 
4184         assertEquals(0, fm.getFocusedIndex());
4185         assertEquals(0, fm.getFocusedCell().getRow());
4186         assertEquals(test_rt_38892_firstNameCol, fm.getFocusedCell().getTableColumn());
4187         assertEquals(0, fm.getFocusedCell().getColumn());
4188     }
4189 
4190     @Test public void test_rt_38892_removeLeftMostColumn() {
4191         TableView<Person> table = init_test_rt_38892();
4192 
4193         TableView.TableViewFocusModel<Person> fm = table.getFocusModel();
4194         fm.focus(0, test_rt_38892_firstNameCol);
4195 
4196         // assert pre-conditions
4197         assertEquals(0, fm.getFocusedIndex());
4198         assertEquals(0, fm.getFocusedCell().getRow());
4199         assertEquals(test_rt_38892_firstNameCol, fm.getFocusedCell().getTableColumn());
4200         assertEquals(0, fm.getFocusedCell().getColumn());
4201 
4202         // now remove column where focus is and replace it with a new column.
4203         // In the current (non-specified) behavior, this results in focus being
4204         // shifted to a cell in the remaining column, even when we add a new column
4205         // as we index based on the column, not on its index.
4206         table.getColumns().remove(0);
4207         TableColumn<Person,String> newColumn = columnCallable.get();
4208         table.getColumns().add(0, newColumn);
4209 
4210         assertEquals(0, fm.getFocusedIndex());
4211         assertEquals(0, fm.getFocusedCell().getRow());
4212         assertEquals(test_rt_38892_lastNameCol, fm.getFocusedCell().getTableColumn());
4213         assertEquals(1, fm.getFocusedCell().getColumn());
4214     }
4215 
4216     @Test public void test_rt_38892_removeSelectionFromCellsInRemovedColumn() {
4217         TableView<Person> table = init_test_rt_38892();
4218 
4219         TableView.TableViewSelectionModel sm = table.getSelectionModel();
4220         sm.select(0, test_rt_38892_firstNameCol);
4221         sm.select(1, test_rt_38892_lastNameCol);    // this should go
4222         sm.select(2, test_rt_38892_firstNameCol);
4223         sm.select(3, test_rt_38892_lastNameCol);    // so should this
4224         sm.select(4, test_rt_38892_firstNameCol);
4225 
4226         assertEquals(5, sm.getSelectedCells().size());
4227 
4228         table.getColumns().remove(1);
4229 
4230         assertEquals(3, sm.getSelectedCells().size());
4231         assertTrue(sm.isSelected(0, test_rt_38892_firstNameCol));
4232         assertFalse(sm.isSelected(1, test_rt_38892_lastNameCol));
4233         assertTrue(sm.isSelected(2, test_rt_38892_firstNameCol));
4234         assertFalse(sm.isSelected(3, test_rt_38892_lastNameCol));
4235         assertTrue(sm.isSelected(4, test_rt_38892_firstNameCol));
4236     }
4237 
4238     @Test public void test_rt_38787_remove_b() {
4239         // selection moves to "a"
4240         test_rt_38787("a", 0, "b");
4241     }
4242 
4243     @Test public void test_rt_38787_remove_b_c() {
4244         // selection moves to "a"
4245         test_rt_38787("a", 0, "b", "c");
4246     }
4247 
4248     @Test public void test_rt_38787_remove_c_d() {
4249         // selection stays on "b"
4250         test_rt_38787("b", 1, "c", "d");
4251     }
4252 
4253     @Test public void test_rt_38787_remove_a() {
4254         // selection moves to "b", now in index 0
4255         test_rt_38787("b", 0, "a");
4256     }
4257 
4258     @Test public void test_rt_38787_remove_z() {
4259         // selection shouldn't move as 'z' doesn't exist
4260         test_rt_38787("b", 1, "z");
4261     }
4262 
4263     private void test_rt_38787(String expectedItem, int expectedIndex, String... itemsToRemove) {
4264         TableView<String> stringTableView = new TableView<>();
4265         stringTableView.getItems().addAll("a","b","c","d");
4266 
4267         TableColumn<String,String> column = new TableColumn<>("Column");
4268         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4269         stringTableView.getColumns().add(column);
4270 
4271         MultipleSelectionModel<String> sm = stringTableView.getSelectionModel();
4272         sm.select("b");
4273 
4274         // test pre-conditions
4275         assertEquals(1, sm.getSelectedIndex());
4276         assertEquals(1, (int)sm.getSelectedIndices().get(0));
4277         assertEquals("b", sm.getSelectedItem());
4278         assertEquals("b", sm.getSelectedItems().get(0));
4279         assertFalse(sm.isSelected(0));
4280         assertTrue(sm.isSelected(1));
4281         assertFalse(sm.isSelected(2));
4282 
4283         // removing items
4284         stringTableView.getItems().removeAll(itemsToRemove);
4285 
4286         // testing against expectations
4287         assertEquals(expectedIndex, sm.getSelectedIndex());
4288         assertEquals(expectedIndex, (int)sm.getSelectedIndices().get(0));
4289         assertEquals(expectedItem, sm.getSelectedItem());
4290         assertEquals(expectedItem, sm.getSelectedItems().get(0));
4291     }
4292 
4293     private int rt_38341_indices_count = 0;
4294     private int rt_38341_items_count = 0;
4295     @Test public void test_rt_38341() {
4296         TableView<String> stringTableView = new TableView<>();
4297         stringTableView.getItems().addAll("a","b","c","d");
4298 
4299         TableColumn<String,String> column = new TableColumn<>("Column");
4300         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4301         stringTableView.getColumns().add(column);
4302 
4303         MultipleSelectionModel<String> sm = stringTableView.getSelectionModel();
4304         sm.getSelectedIndices().addListener((ListChangeListener<Integer>) c -> {
4305             rt_38341_indices_count++;
4306         });
4307         sm.getSelectedItems().addListener((ListChangeListener<String>) c -> rt_38341_items_count++);
4308 
4309         assertEquals(0, rt_38341_indices_count);
4310         assertEquals(0, rt_38341_items_count);
4311 
4312         // expand the first child of root, and select it (note: root isn't visible)
4313         sm.select(1);
4314         assertEquals(1, sm.getSelectedIndex());
4315         assertEquals(1, sm.getSelectedIndices().size());
4316         assertEquals(1, (int)sm.getSelectedIndices().get(0));
4317         assertEquals(1, sm.getSelectedItems().size());
4318         assertEquals("b", sm.getSelectedItem());
4319         assertEquals("b", sm.getSelectedItems().get(0));
4320 
4321         assertEquals(1, rt_38341_indices_count);
4322         assertEquals(1, rt_38341_items_count);
4323 
4324         // now delete it
4325         stringTableView.getItems().remove(1);
4326 
4327         // selection should move to the childs parent in index 0
4328         assertEquals(0, sm.getSelectedIndex());
4329         assertEquals(1, sm.getSelectedIndices().size());
4330         assertEquals(0, (int)sm.getSelectedIndices().get(0));
4331         assertEquals(1, sm.getSelectedItems().size());
4332         assertEquals("a", sm.getSelectedItem());
4333         assertEquals("a", sm.getSelectedItems().get(0));
4334 
4335         // we also expect there to be an event in the selection model for
4336         // selected indices and selected items
4337         assertEquals(2, rt_38341_indices_count);
4338         assertEquals(2, rt_38341_items_count);
4339     }
4340 
4341     @Test public void test_rt_39132() {
4342         TableView<String> tableView = new TableView<>();
4343 
4344         ObservableList items = FXCollections.observableArrayList("one", "two", "three");
4345         tableView.setItems(items);
4346 
4347         TableColumn<String,String> column = new TableColumn<>("Column");
4348         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4349         tableView.getColumns().add(column);
4350 
4351         MultipleSelectionModel sm = tableView.getSelectionModel();
4352         sm.select(0);
4353 
4354         assertEquals(0, sm.getSelectedIndex());
4355         assertEquals("one", sm.getSelectedItem());
4356 
4357         items.add(0, "new item");
4358         assertEquals(1, sm.getSelectedIndex());
4359         assertEquals("one", sm.getSelectedItem());
4360     }
4361 
4362     private int rt_38943_index_count = 0;
4363     private int rt_38943_item_count = 0;
4364     @Test public void test_rt_38943() {
4365         TableView<String> tableView = new TableView<>(FXCollections.observableArrayList("one", "two", "three"));
4366 
4367         TableColumn<String,String> column = new TableColumn<>("Column");
4368         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4369         tableView.getColumns().add(column);
4370 
4371         MultipleSelectionModel sm = tableView.getSelectionModel();
4372 
4373         sm.selectedIndexProperty().addListener((observable, oldValue, newValue) -> rt_38943_index_count++);
4374         sm.selectedItemProperty().addListener((observable, oldValue, newValue) -> rt_38943_item_count++);
4375 
4376         assertEquals(-1, sm.getSelectedIndex());
4377         assertNull(sm.getSelectedItem());
4378         assertEquals(0, rt_38943_index_count);
4379         assertEquals(0, rt_38943_item_count);
4380 
4381         sm.select(0);
4382         assertEquals(0, sm.getSelectedIndex());
4383         assertEquals("one", sm.getSelectedItem());
4384         assertEquals(1, rt_38943_index_count);
4385         assertEquals(1, rt_38943_item_count);
4386 
4387         sm.clearSelection(0);
4388         assertEquals(-1, sm.getSelectedIndex());
4389         assertNull(sm.getSelectedItem());
4390         assertEquals(2, rt_38943_index_count);
4391         assertEquals(2, rt_38943_item_count);
4392     }
4393 
4394     @Test public void test_rt_38884() {
4395         TableView<String> table = new TableView<>();
4396         ObservableList<String> items = table.getItems();
4397 
4398         table.getSelectionModel().getSelectedItems().addListener((ListChangeListener.Change<? extends String> c) -> {
4399             while (c.next()) {
4400                 if (c.wasRemoved()) {
4401                     assertTrue(c.getRemovedSize() > 0);
4402 
4403                     List<? extends String> removed = c.getRemoved();
4404                     String removedItem = null;
4405                     try {
4406                         removedItem = removed.get(0);
4407                     } catch (Exception e) {
4408                         fail();
4409                     }
4410 
4411                     assertEquals("foo", removedItem);
4412                 }
4413             }
4414         });
4415 
4416         items.add("foo");
4417         table.getSelectionModel().select(0);
4418         items.clear();
4419     }
4420 
4421     private int rt_37360_add_count = 0;
4422     private int rt_37360_remove_count = 0;
4423     @Test public void test_rt_37360() {
4424         TableView<String> stringTableView = new TableView<>();
4425         stringTableView.getItems().addAll("a","b");
4426 
4427         TableColumn<String,String> column = new TableColumn<>("Column");
4428         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4429         stringTableView.getColumns().add(column);
4430 
4431         MultipleSelectionModel<String> sm = stringTableView.getSelectionModel();
4432         sm.setSelectionMode(SelectionMode.MULTIPLE);
4433         sm.getSelectedItems().addListener((ListChangeListener<String>) c -> {
4434             while (c.next()) {
4435                 if (c.wasAdded()) {
4436                     rt_37360_add_count += c.getAddedSize();
4437                 }
4438                 if (c.wasRemoved()) {
4439                     rt_37360_remove_count += c.getRemovedSize();
4440                 }
4441             }
4442         });
4443 
4444         assertEquals(0, sm.getSelectedItems().size());
4445         assertEquals(0, rt_37360_add_count);
4446         assertEquals(0, rt_37360_remove_count);
4447 
4448         sm.select(0);
4449         assertEquals(1, sm.getSelectedItems().size());
4450         assertEquals(1, rt_37360_add_count);
4451         assertEquals(0, rt_37360_remove_count);
4452 
4453         sm.select(1);
4454         assertEquals(2, sm.getSelectedItems().size());
4455         assertEquals(2, rt_37360_add_count);
4456         assertEquals(0, rt_37360_remove_count);
4457 
4458         sm.clearAndSelect(1);
4459         assertEquals(1, sm.getSelectedItems().size());
4460         assertEquals(2, rt_37360_add_count);
4461         assertEquals(1, rt_37360_remove_count);
4462     }
4463 
4464     @Test public void test_rt_38491() {
4465         TableView<String> stringTableView = new TableView<>();
4466         stringTableView.getItems().addAll("a","b", "c", "d");
4467 
4468         TableColumn<String,String> column = new TableColumn<>("Column");
4469         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4470         stringTableView.getColumns().add(column);
4471 
4472         TableSelectionModel<String> sm = stringTableView.getSelectionModel();
4473         sm.setSelectionMode(SelectionMode.MULTIPLE);
4474 
4475         TableView.TableViewFocusModel fm = stringTableView.getFocusModel();
4476 
4477         StageLoader sl = new StageLoader(stringTableView);
4478 
4479         // click on row 0
4480         sm.select(0, column);
4481         assertTrue(sm.isSelected(0));
4482         assertEquals("a", sm.getSelectedItem());
4483         assertTrue(fm.isFocused(0));
4484         assertEquals("a", fm.getFocusedItem());
4485         assertEquals(0, fm.getFocusedCell().getRow());
4486         assertEquals(column, fm.getFocusedCell().getTableColumn());
4487 
4488         TablePosition anchor = TableCellBehavior.getAnchor(stringTableView, null);
4489         assertTrue(TableCellBehavior.hasNonDefaultAnchor(stringTableView));
4490         assertEquals(0, anchor.getRow());
4491         assertEquals(column, anchor.getTableColumn());
4492 
4493         // now add a new item at row 0. This has the effect of pushing down
4494         // the selected item into row 1.
4495         stringTableView.getItems().add(0, "z");
4496 
4497         Toolkit.getToolkit().firePulse();
4498 
4499         // The first bug was that selection and focus were not moving down to
4500         // be on row 1, so we test that now
4501         assertFalse(sm.isSelected(0));
4502         assertFalse(fm.isFocused(0));
4503         assertTrue(sm.isSelected(1));
4504         assertEquals("a", sm.getSelectedItem());
4505         assertTrue(fm.isFocused(1));
4506         assertEquals("a", fm.getFocusedItem());
4507         assertEquals(1, fm.getFocusedCell().getRow());
4508         assertEquals(column, fm.getFocusedCell().getTableColumn());
4509 
4510         // The second bug was that the anchor was not being pushed down as well
4511         // (when it should).
4512         anchor = TableCellBehavior.getAnchor(stringTableView, null);
4513         assertTrue(TableCellBehavior.hasNonDefaultAnchor(stringTableView));
4514         assertEquals(1, anchor.getRow());
4515         assertEquals(column, anchor.getTableColumn());
4516 
4517         sl.dispose();
4518     }
4519 
4520     private final ObservableList<String> rt_39256_list = FXCollections.observableArrayList();
4521     @Test public void test_rt_39256() {
4522         TableView<String> stringTableView = new TableView<>();
4523         stringTableView.getItems().addAll("a","b", "c", "d");
4524 
4525         TableColumn<String,String> column = new TableColumn<>("Column");
4526         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4527         stringTableView.getColumns().add(column);
4528 
4529         TableSelectionModel<String> sm = stringTableView.getSelectionModel();
4530         sm.setSelectionMode(SelectionMode.MULTIPLE);
4531 
4532 //        rt_39256_list.addListener((ListChangeListener<String>) change -> {
4533 //            while (change.next()) {
4534 //                System.err.println("number of selected persons (in bound list): " + change.getList().size());
4535 //            }
4536 //        });
4537 
4538         Bindings.bindContent(rt_39256_list, sm.getSelectedItems());
4539 
4540         assertEquals(0, sm.getSelectedItems().size());
4541         assertEquals(0, rt_39256_list.size());
4542 
4543         sm.selectAll();
4544         assertEquals(4, sm.getSelectedItems().size());
4545         assertEquals(4, rt_39256_list.size());
4546 
4547         sm.selectAll();
4548         assertEquals(4, sm.getSelectedItems().size());
4549         assertEquals(4, rt_39256_list.size());
4550 
4551         sm.selectAll();
4552         assertEquals(4, sm.getSelectedItems().size());
4553         assertEquals(4, rt_39256_list.size());
4554     }
4555 
4556     @Test public void test_rt_39256_noTableView() {
4557         ObservableList<String> listOne = FXCollections.observableArrayList();
4558         ObservableList<String> listTwo = FXCollections.observableArrayList();
4559 
4560 //        listOne.addListener((ListChangeListener<String>) change -> {
4561 //            while (change.next()) {
4562 //                System.out.println("number of strings in bound list - listOne: " + change.getList().size() + ", change: " + change);
4563 //            }
4564 //        });
4565 //
4566 //        listTwo.addListener((ListChangeListener<String>) change -> {
4567 //            while (change.next()) {
4568 //                System.out.println("number of strings in unbound list - listTwo: " + change.getList().size() + ", change: " + change);
4569 //            }
4570 //        });
4571 
4572         Bindings.bindContent(listOne, listTwo);
4573 
4574         assertEquals(0, listOne.size());
4575         assertEquals(0, listTwo.size());
4576 
4577         System.out.println("Test One:");
4578         listTwo.setAll("a", "b", "c", "d");
4579         assertEquals(4, listOne.size());
4580         assertEquals(4, listTwo.size());
4581 
4582         System.out.println("\nTest Two:");
4583         listTwo.setAll("e", "f", "g", "h");
4584         assertEquals(4, listOne.size());
4585         assertEquals(4, listTwo.size());
4586 
4587         System.out.println("\nTest Three:");
4588         listTwo.setAll("i", "j", "k", "l");
4589         assertEquals(4, listOne.size());
4590         assertEquals(4, listTwo.size());
4591     }
4592 
4593     private final ObservableList<String> rt_39482_list = FXCollections.observableArrayList();
4594     @Test public void test_rt_39482() {
4595         TableView<String> stringTableView = new TableView<>();
4596         stringTableView.getItems().addAll("a","b", "c", "d");
4597 
4598         TableColumn<String,String> column = new TableColumn<>("Column");
4599         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4600         stringTableView.getColumns().add(column);
4601 
4602         TableView.TableViewSelectionModel<String> sm = stringTableView.getSelectionModel();
4603         sm.setSelectionMode(SelectionMode.MULTIPLE);
4604 
4605         sm.getSelectedItems().addListener((ListChangeListener<String>) change -> {
4606             while (change.next()) {
4607                 System.out.println("sm.getSelectedItems(): " + change.getList());
4608             }
4609         });
4610 
4611         rt_39482_list.addListener((ListChangeListener<String>) change -> {
4612             while (change.next()) {
4613                 System.out.println("rt_39482_list: " + change.getList());
4614             }
4615         });
4616 
4617         Bindings.bindContent(rt_39482_list, sm.getSelectedItems());
4618 
4619         assertEquals(0, sm.getSelectedItems().size());
4620         assertEquals(0, rt_39482_list.size());
4621 
4622         test_rt_39482_selectRow("a", sm, 0, column);
4623         test_rt_39482_selectRow("b", sm, 1, column);
4624         test_rt_39482_selectRow("c", sm, 2, column);
4625         test_rt_39482_selectRow("d", sm, 3, column);
4626     }
4627 
4628     private void test_rt_39482_selectRow(String expectedString,
4629                                          TableView.TableViewSelectionModel<String> sm,
4630                                          int rowToSelect,
4631                                          TableColumn<String,String> columnToSelect) {
4632         System.out.println("\nSelect row " + rowToSelect);
4633         sm.selectAll();
4634         assertEquals(4, sm.getSelectedCells().size());
4635         assertEquals(4, sm.getSelectedIndices().size());
4636         assertEquals(4, sm.getSelectedItems().size());
4637         assertEquals(4, rt_39482_list.size());
4638 
4639         sm.clearAndSelect(rowToSelect, columnToSelect);
4640         assertEquals(1, sm.getSelectedCells().size());
4641         assertEquals(1, sm.getSelectedIndices().size());
4642         assertEquals(1, sm.getSelectedItems().size());
4643         assertEquals(expectedString, sm.getSelectedItem());
4644         assertEquals(expectedString, rt_39482_list.get(0));
4645         assertEquals(1, rt_39482_list.size());
4646     }
4647 
4648     @Test public void test_rt_39559_useSM_selectAll() {
4649         test_rt_39559(true);
4650     }
4651 
4652     @Test public void test_rt_39559_useKeyboard_selectAll() {
4653         test_rt_39559(false);
4654     }
4655 
4656     private void test_rt_39559(boolean useSMSelectAll) {
4657         TableView<String> stringTableView = new TableView<>();
4658         stringTableView.getItems().addAll("a","b", "c", "d");
4659 
4660         TableColumn<String,String> column = new TableColumn<>("Column");
4661         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4662         stringTableView.getColumns().add(column);
4663 
4664         TableView.TableViewSelectionModel<String> sm = stringTableView.getSelectionModel();
4665         sm.setSelectionMode(SelectionMode.MULTIPLE);
4666 
4667         StageLoader sl = new StageLoader(stringTableView);
4668         KeyEventFirer keyboard = new KeyEventFirer(stringTableView);
4669 
4670         assertEquals(0, sm.getSelectedItems().size());
4671 
4672         sm.clearAndSelect(0);
4673 
4674         if (useSMSelectAll) {
4675             sm.selectAll();
4676         } else {
4677             keyboard.doKeyPress(KeyCode.A, KeyModifier.getShortcutKey());
4678         }
4679 
4680         assertEquals(4, sm.getSelectedItems().size());
4681         assertEquals(0, ((TablePosition) TableCellBehavior.getAnchor(stringTableView, null)).getRow());
4682 
4683         keyboard.doKeyPress(KeyCode.DOWN, KeyModifier.SHIFT);
4684 
4685         assertEquals(0, ((TablePosition) TableCellBehavior.getAnchor(stringTableView, null)).getRow());
4686         assertEquals(2, sm.getSelectedItems().size());
4687         assertEquals("a", sm.getSelectedItems().get(0));
4688         assertEquals("b", sm.getSelectedItems().get(1));
4689 
4690         sl.dispose();
4691     }
4692 
4693     @Test public void test_rt_16068_firstElement_selectAndRemoveSameRow() {
4694         // select and then remove the 'a' item, selection and focus should both
4695         // stay at the first row, now 'b'
4696         test_rt_16068(0, 0, 0);
4697     }
4698 
4699     @Test public void test_rt_16068_firstElement_selectRowAndRemoveLaterSibling() {
4700         // select row 'a', and remove row 'c', selection and focus should not change
4701         test_rt_16068(0, 2, 0);
4702     }
4703 
4704     @Test public void test_rt_16068_middleElement_selectAndRemoveSameRow() {
4705         // select and then remove the 'b' item, selection and focus should both
4706         // move up one row to the 'a' item
4707         test_rt_16068(1, 1, 0);
4708     }
4709 
4710     @Test public void test_rt_16068_middleElement_selectRowAndRemoveLaterSibling() {
4711         // select row 'b', and remove row 'c', selection and focus should not change
4712         test_rt_16068(1, 2, 1);
4713     }
4714 
4715     @Test public void test_rt_16068_middleElement_selectRowAndRemoveEarlierSibling() {
4716         // select row 'b', and remove row 'a', selection and focus should move up
4717         // one row, remaining on 'b'
4718         test_rt_16068(1, 0, 0);
4719     }
4720 
4721     @Test public void test_rt_16068_lastElement_selectAndRemoveSameRow() {
4722         // select and then remove the 'd' item, selection and focus should both
4723         // move up one row to the 'c' item
4724         test_rt_16068(3, 3, 2);
4725     }
4726 
4727     @Test public void test_rt_16068_lastElement_selectRowAndRemoveEarlierSibling() {
4728         // select row 'd', and remove row 'a', selection and focus should move up
4729         // one row, remaining on 'd'
4730         test_rt_16068(3, 0, 2);
4731     }
4732 
4733     private void test_rt_16068(int indexToSelect, int indexToRemove, int expectedIndex) {
4734         TableView<String> stringTableView = new TableView<>();
4735         stringTableView.getItems().addAll("a","b", "c", "d");
4736 
4737         TableColumn<String,String> column = new TableColumn<>("Column");
4738         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4739         stringTableView.getColumns().add(column);
4740 
4741         TableView.TableViewSelectionModel<?> sm = stringTableView.getSelectionModel();
4742         FocusModel<?> fm = stringTableView.getFocusModel();
4743 
4744         sm.select(indexToSelect);
4745         assertEquals(indexToSelect, sm.getSelectedIndex());
4746         assertEquals(stringTableView.getItems().get(indexToSelect), sm.getSelectedItem());
4747         assertEquals(indexToSelect, fm.getFocusedIndex());
4748         assertEquals(stringTableView.getItems().get(indexToSelect), fm.getFocusedItem());
4749 
4750         stringTableView.getItems().remove(indexToRemove);
4751         assertEquals(expectedIndex, sm.getSelectedIndex());
4752         assertEquals(stringTableView.getItems().get(expectedIndex), sm.getSelectedItem());
4753         assertEquals(expectedIndex, fm.getFocusedIndex());
4754         assertEquals(stringTableView.getItems().get(expectedIndex), fm.getFocusedItem());
4755     }
4756 
4757     private int test_rt_39822_count = 0;
4758     @Test public void test_rt_39822() {
4759         // get the current exception handler before replacing with our own,
4760         // as ListListenerHelp intercepts the exception otherwise
4761         final Thread.UncaughtExceptionHandler exceptionHandler = Thread.currentThread().getUncaughtExceptionHandler();
4762         Thread.currentThread().setUncaughtExceptionHandler((t, e) -> {
4763             e.printStackTrace();
4764 
4765             if (test_rt_39822_count == 0) {
4766                 test_rt_39822_count++;
4767                 if (! (e instanceof IllegalStateException)) {
4768                     fail("Incorrect exception type - expecting IllegalStateException");
4769                 }
4770             } else {
4771                 // don't care
4772                 test_rt_39822_count++;
4773             }
4774         });
4775 
4776         TableView<String> table = new TableView<>();
4777         TableColumn<String, String> col1 = new TableColumn<>("Foo");
4778         table.getColumns().addAll(col1, col1);  // add column twice
4779 
4780         StageLoader sl = null;
4781         try {
4782             sl = new StageLoader(table);
4783         } finally {
4784             if (sl != null) {
4785                 sl.dispose();
4786             }
4787 
4788             // reset the exception handler
4789             Thread.currentThread().setUncaughtExceptionHandler(exceptionHandler);
4790         }
4791     }
4792 
4793     private int test_rt_39842_count = 0;
4794     @Test public void test_rt_39842_selectLeftDown() {
4795         test_rt_39842(true, false);
4796     }
4797 
4798     @Test public void test_rt_39842_selectLeftUp() {
4799         test_rt_39842(true, true);
4800     }
4801 
4802     @Test public void test_rt_39842_selectRightDown() {
4803         test_rt_39842(false, false);
4804     }
4805 
4806     @Test public void test_rt_39842_selectRightUp() {
4807         test_rt_39842(false, true);
4808     }
4809 
4810     private void test_rt_39842(boolean selectToLeft, boolean selectUpwards) {
4811         test_rt_39842_count = 0;
4812 
4813         TableColumn firstNameCol = new TableColumn("First Name");
4814         firstNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("firstName"));
4815 
4816         TableColumn lastNameCol = new TableColumn("Last Name");
4817         lastNameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("lastName"));
4818 
4819         TableView<Person> table = new TableView<>();
4820         table.setItems(personTestData);
4821         table.getColumns().addAll(firstNameCol, lastNameCol);
4822 
4823         sm = table.getSelectionModel();
4824         sm.setCellSelectionEnabled(true);
4825         sm.setSelectionMode(SelectionMode.MULTIPLE);
4826         sm.getSelectedCells().addListener((ListChangeListener) c -> test_rt_39842_count++);
4827 
4828         StageLoader sl = new StageLoader(table);
4829 
4830         assertEquals(0, test_rt_39842_count);
4831 
4832         if (selectToLeft) {
4833             if (selectUpwards) {
4834                 sm.selectRange(3, lastNameCol, 0, firstNameCol);
4835             } else {
4836                 sm.selectRange(0, lastNameCol, 3, firstNameCol);
4837             }
4838         } else {
4839             if (selectUpwards) {
4840                 sm.selectRange(3, firstNameCol, 0, lastNameCol);
4841             } else {
4842                 sm.selectRange(0, firstNameCol, 3, lastNameCol);
4843             }
4844         }
4845 
4846         // test model state
4847         assertEquals(8, sm.getSelectedCells().size());
4848         assertEquals(1, test_rt_39842_count);
4849 
4850         // test visual state
4851         for (int row = 0; row <= 3; row++) {
4852             for (int column = 0; column <= 1; column++) {
4853                 IndexedCell cell = VirtualFlowTestUtils.getCell(table, row, column);
4854                 assertTrue(cell.isSelected());
4855             }
4856         }
4857 
4858         sl.dispose();
4859     }
4860 
4861     @Test public void test_rt_22599() {
4862         ObservableList<RT22599_DataType> initialData = FXCollections.observableArrayList(
4863                 new RT22599_DataType(1, "row1"),
4864                 new RT22599_DataType(2, "row2"),
4865                 new RT22599_DataType(3, "row3")
4866         );
4867 
4868         TableColumn<RT22599_DataType, String> col = new TableColumn<>("Header");
4869         col.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue().text));
4870 
4871         TableView<RT22599_DataType> table = new TableView<>();
4872         table.setItems(initialData);
4873         table.getColumns().addAll(col);
4874 
4875         StageLoader sl = new StageLoader(table);
4876 
4877         // testing initial state
4878         assertNotNull(table.getSkin());
4879         assertEquals("row1", VirtualFlowTestUtils.getCell(table, 0, 0).getText());
4880         assertEquals("row2", VirtualFlowTestUtils.getCell(table, 1, 0).getText());
4881         assertEquals("row3", VirtualFlowTestUtils.getCell(table, 2, 0).getText());
4882 
4883         // change row 0 (where "row1" currently resides), keeping same id.
4884         // Because 'set' is called, the control should update to the new content
4885         // without any user interaction
4886         RT22599_DataType data;
4887         initialData.set(0, data = new RT22599_DataType(0, "row1a"));
4888         Toolkit.getToolkit().firePulse();
4889         assertEquals("row1a", VirtualFlowTestUtils.getCell(table, 0, 0).getText());
4890 
4891         // change the row 0 (where we currently have "row1a") value directly.
4892         // Because there is no associated property, this won't be observed, so
4893         // the control should still show "row1a" rather than "row1b"
4894         data.text = "row1b";
4895         Toolkit.getToolkit().firePulse();
4896         assertEquals("row1a", VirtualFlowTestUtils.getCell(table, 0, 0).getText());
4897 
4898         // call refresh() to force a refresh of all visible cells
4899         table.refresh();
4900         Toolkit.getToolkit().firePulse();
4901         assertEquals("row1b", VirtualFlowTestUtils.getCell(table, 0, 0).getText());
4902 
4903         sl.dispose();
4904     }
4905 
4906     private static class RT22599_DataType {
4907         public int id = 0;
4908         public String text = "";
4909 
4910         public RT22599_DataType(int id, String text) {
4911             this.id = id;
4912             this.text = text;
4913         }
4914 
4915         @Override public boolean equals(Object obj) {
4916             if (obj == null) return false;
4917             return id == ((RT22599_DataType)obj).id;
4918         }
4919     }
4920 
4921     private int rt_39966_count = 0;
4922     @Test public void test_rt_39966() {
4923         ObservableList<String> initialData = FXCollections.observableArrayList("Hello World");
4924 
4925         TableColumn<String, String> col = new TableColumn<>("Header");
4926         col.setCellValueFactory(param -> new ReadOnlyStringWrapper(param.getValue()));
4927 
4928         TableView<String> table = new TableView<>(initialData);
4929         table.getColumns().addAll(col);
4930 
4931         StageLoader sl = new StageLoader(table);
4932 
4933         // initially there is no selection
4934         assertTrue(table.getSelectionModel().isEmpty());
4935 
4936         table.getSelectionModel().selectedItemProperty().addListener((value, s1, s2) -> {
4937             if (rt_39966_count == 0) {
4938                 rt_39966_count++;
4939                 assertFalse(table.getSelectionModel().isEmpty());
4940             } else {
4941                 assertTrue(table.getSelectionModel().isEmpty());
4942             }
4943         });
4944 
4945         // our assertion two lines down always succeeds. What fails is our
4946         // assertion above within the listener.
4947         table.getSelectionModel().select(0);
4948         assertFalse(table.getSelectionModel().isEmpty());
4949 
4950         initialData.remove(0);
4951         assertTrue(table.getSelectionModel().isEmpty());
4952 
4953         sl.dispose();
4954     }
4955 
4956     /**
4957      * Bullet 1: selected index must be updated
4958      * Corner case: last selected. Fails for core
4959      */
4960     @Test public void test_rt_40012_selectedAtLastOnDisjointRemoveItemsAbove() {
4961         ObservableList<String> items = FXCollections.observableArrayList("0", "1", "2", "3", "4", "5");
4962         TableView<String> stringTableView = new TableView<>(items);
4963         TableView.TableViewSelectionModel<?> sm = stringTableView.getSelectionModel();
4964 
4965         TableColumn<String,String> column = new TableColumn<>("Column");
4966         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4967         stringTableView.getColumns().add(column);
4968 
4969         int last = items.size() - 1;
4970 
4971         // selecting item "5"
4972         sm.select(last);
4973 
4974         // disjoint remove of 2 elements above the last selected
4975         // Removing "1" and "3"
4976         items.removeAll(items.get(1), items.get(3));
4977 
4978         // selection should move up two places such that it remains on item "5",
4979         // but in index (last - 2).
4980         int expected = last - 2;
4981         assertEquals("5", sm.getSelectedItem());
4982         assertEquals("selected index after disjoint removes above", expected, sm.getSelectedIndex());
4983     }
4984 
4985     /**
4986      * Variant of 1: if selectedIndex is not updated,
4987      * the old index is no longer valid
4988      * for accessing the items.
4989      */
4990     @Test public void test_rt_40012_accessSelectedAtLastOnDisjointRemoveItemsAbove() {
4991         ObservableList<String> items = FXCollections.observableArrayList("0", "1", "2", "3", "4", "5");
4992         TableView<String> stringTableView = new TableView<>(items);
4993         TableView.TableViewSelectionModel<?> sm = stringTableView.getSelectionModel();
4994 
4995         TableColumn<String,String> column = new TableColumn<>("Column");
4996         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
4997         stringTableView.getColumns().add(column);
4998 
4999         int last = items.size() - 1;
5000 
5001         // selecting item "5"
5002         sm.select(last);
5003 
5004         // disjoint remove of 2 elements above the last selected
5005         items.removeAll(items.get(1), items.get(3));
5006         int selected = sm.getSelectedIndex();
5007         if (selected > -1) {
5008             items.get(selected);
5009         }
5010     }
5011 
5012     /**
5013      * Bullet 2: selectedIndex notification count
5014      *
5015      * Note that we don't use the corner case of having the last index selected
5016      * (which fails already on updating the index)
5017      */
5018     private int rt_40012_count = 0;
5019     @Test public void test_rt_40012_selectedIndexNotificationOnDisjointRemovesAbove() {
5020         ObservableList<String> items = FXCollections.observableArrayList("0", "1", "2", "3", "4", "5");
5021         TableView<String> stringTableView = new TableView<>(items);
5022         TableView.TableViewSelectionModel<?> sm = stringTableView.getSelectionModel();
5023 
5024         TableColumn<String,String> column = new TableColumn<>("Column");
5025         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
5026         stringTableView.getColumns().add(column);
5027 
5028         int last = items.size() - 2;
5029         sm.select(last);
5030         assertEquals(last, sm.getSelectedIndex());
5031 
5032         rt_40012_count = 0;
5033         sm.selectedIndexProperty().addListener(o -> rt_40012_count++);
5034 
5035         // disjoint remove of 2 elements above the last selected
5036         items.removeAll(items.get(1), items.get(3));
5037         assertEquals("sanity: selectedIndex must be shifted by -2", last - 2, sm.getSelectedIndex());
5038         assertEquals("must fire single event on removes above", 1, rt_40012_count);
5039     }
5040 
5041     /**
5042      * Bullet 3: unchanged selectedItem must not fire change
5043      */
5044     @Test
5045     public void test_rt_40012_selectedItemNotificationOnDisjointRemovesAbove() {
5046         ObservableList<String> items = FXCollections.observableArrayList("0", "1", "2", "3", "4", "5");
5047         TableView<String> stringTableView = new TableView<>(items);
5048         TableView.TableViewSelectionModel<?> sm = stringTableView.getSelectionModel();
5049 
5050         TableColumn<String,String> column = new TableColumn<>("Column");
5051         column.setCellValueFactory(cdf -> new ReadOnlyStringWrapper(cdf.getValue()));
5052         stringTableView.getColumns().add(column);
5053 
5054         int last = items.size() - 2;
5055         Object lastItem = items.get(last);
5056         sm.select(last);
5057         assertEquals(lastItem, sm.getSelectedItem());
5058 
5059         rt_40012_count = 0;
5060         sm.selectedItemProperty().addListener(o -> rt_40012_count++);
5061 
5062         // disjoint remove of 2 elements above the last selected
5063         items.removeAll(items.get(1), items.get(3));
5064         assertEquals("sanity: selectedItem unchanged", lastItem, sm.getSelectedItem());
5065         assertEquals("must not fire on unchanged selected item", 0, rt_40012_count);
5066     }
5067 
5068     /**
5069      * ClearAndSelect fires invalid change event if selectedIndex is unchanged.
5070      */
5071     private int rt_40212_count = 0;
5072     @Test public void test_rt_40212() {
5073         final TableView<Number> table = new TableView<>();
5074         for (int i = 0; i < 10; i++) {
5075             table.getItems().add(i);
5076         }
5077 
5078         TableColumn<Number,Number> column = new TableColumn<>("Column");
5079         column.setCellValueFactory(cdf -> new ReadOnlyIntegerWrapper((int) cdf.getValue()));
5080         table.getColumns().add(column);
5081 
5082         MultipleSelectionModel<Number> sm = table.getSelectionModel();
5083         sm.setSelectionMode(SelectionMode.MULTIPLE);
5084 
5085         sm.selectRange(3, 5);
5086         int selected = sm.getSelectedIndex();
5087 
5088         sm.getSelectedIndices().addListener((ListChangeListener<Integer>) change -> {
5089             assertEquals("sanity: selectedIndex unchanged", selected, sm.getSelectedIndex());
5090             while(change.next()) {
5091                 assertEquals("single event on clearAndSelect already selected", 1, ++rt_40212_count);
5092 
5093                 boolean type = change.wasAdded() || change.wasRemoved() || change.wasPermutated() || change.wasUpdated();
5094                 assertTrue("at least one of the change types must be true", type);
5095             }
5096         });
5097 
5098         sm.clearAndSelect(selected);
5099     }
5100 
5101     @Test public void test_rt_40280() {
5102         final TableView<String> view = new TableView<>();
5103         StageLoader sl = new StageLoader(view);
5104         view.getSelectionModel().getFocusedIndex();
5105         view.getFocusModel().getFocusedIndex();
5106         sl.dispose();
5107     }
5108 
5109     /**
5110      * Test list change of selectedIndices on setIndices. Fails for core ..
5111      */
5112     @Test public void test_rt_40263() {
5113         final TableView<Integer> view = new TableView<>();
5114         for (int i = 0; i < 10; i++) {
5115             view.getItems().add(i);
5116         }
5117 
5118         MultipleSelectionModel<Integer> sm = view.getSelectionModel();
5119         sm.setSelectionMode(SelectionMode.MULTIPLE);
5120 
5121         int[] indices = new int[]{2, 5, 7};
5122         ListChangeListener<Integer> l = c -> {
5123             // firstly, we expect only one change
5124             int subChanges = 0;
5125             while(c.next()) {
5126                 subChanges++;
5127             }
5128             assertEquals(1, subChanges);
5129 
5130             // secondly, we expect the added size to be three, as that is the
5131             // number of items selected
5132             c.reset();
5133             c.next();
5134             System.out.println("Added items: " + c.getAddedSubList());
5135             assertEquals(indices.length, c.getAddedSize());
5136             assertArrayEquals(indices, c.getAddedSubList().stream().mapToInt(i -> i).toArray());
5137         };
5138         sm.getSelectedIndices().addListener(l);
5139         sm.selectIndices(indices[0], indices);
5140     }
5141 
5142     @Test public void test_rt_40319_toRight_toBottom()          { test_rt_40319(true, true, false);   }
5143     @Test public void test_rt_40319_toRight_toTop()             { test_rt_40319(true, false, false);  }
5144     @Test public void test_rt_40319_toLeft_toBottom()           { test_rt_40319(false, true, false);  }
5145     @Test public void test_rt_40319_toLeft_toTop()              { test_rt_40319(false, false, false); }
5146     @Test public void test_rt_40319_toRight_toBottom_useMouse() { test_rt_40319(true, true, true);    }
5147     @Test public void test_rt_40319_toRight_toTop_useMouse()    { test_rt_40319(true, false, true);   }
5148     @Test public void test_rt_40319_toLeft_toBottom_useMouse()  { test_rt_40319(false, true, true);   }
5149     @Test public void test_rt_40319_toLeft_toTop_useMouse()     { test_rt_40319(false, false, true);  }
5150 
5151     private void test_rt_40319(boolean toRight, boolean toBottom, boolean useMouse) {
5152         ObservableList<Person> p = FXCollections.observableArrayList();
5153         p.add(new Person("FirstName1", "LastName1", ""));
5154         p.add(new Person("FirstName2", "LastName2", ""));
5155         p.add(new Person("FirstName3", "LastName3", ""));
5156 
5157         TableView<Person> t = new TableView<>(p);
5158         sm = t.getSelectionModel();
5159         sm.setSelectionMode(SelectionMode.MULTIPLE);
5160 
5161         TableColumn<Person, String> c1 = new TableColumn<>("First Name");
5162         c1.setCellValueFactory(new PropertyValueFactory<>("firstname"));
5163         TableColumn<Person, String> c2 = new TableColumn<>("Last Name");
5164         c2.setCellValueFactory(new PropertyValueFactory<>("lastname"));
5165         t.getColumns().addAll(c1, c2);
5166 
5167         final int startIndex = toRight ? 0 : 2;
5168         final int endIndex = toRight ? 2 : 0;
5169         final TableColumn<Person,String> startColumn = toBottom ? c1 : c2;
5170         final TableColumn<Person,String> endColumn = toBottom ? c2 : c1;
5171 
5172         sm.select(startIndex, startColumn);
5173 
5174         if (useMouse) {
5175             Cell endCell = VirtualFlowTestUtils.getCell(t, endIndex, toRight ? 1 : 0);
5176             MouseEventFirer mouse = new MouseEventFirer(endCell);
5177             mouse.fireMousePressAndRelease(KeyModifier.SHIFT);
5178         } else {
5179             t.getSelectionModel().selectRange(startIndex, startColumn, endIndex, endColumn);
5180         }
5181 
5182         assertEquals(3, sm.getSelectedItems().size());
5183         assertEquals(3, sm.getSelectedIndices().size());
5184         assertEquals(3, sm.getSelectedCells().size());
5185     }
5186 
5187     private int rt_40546_count = 0;
5188     @Test public void test_rt_40546() {
5189         ObservableList<Person> p = FXCollections.observableArrayList();
5190 
5191         TableView<Person> t = new TableView<>(p);
5192         sm = t.getSelectionModel();
5193         sm.setSelectionMode(SelectionMode.MULTIPLE);
5194 
5195         TableColumn<Person, String> c1 = new TableColumn<>("First Name");
5196         c1.setCellValueFactory(new PropertyValueFactory<>("firstname"));
5197         TableColumn<Person, String> c2 = new TableColumn<>("Last Name");
5198         c2.setCellValueFactory(new PropertyValueFactory<>("lastname"));
5199         t.getColumns().addAll(c1, c2);
5200 
5201         p.add(new Person("FirstName1", "LastName1", ""));
5202         p.add(new Person("FirstName2", "LastName2", ""));
5203         p.add(new Person("FirstName3", "LastName3", ""));
5204 
5205         // add a listener - although we don't expect to hear anything
5206         sm.getSelectedItems().addListener((ListChangeListener) c -> {
5207             while (c.next()) {
5208                 rt_40546_count++;
5209             }
5210         });
5211 
5212         assertEquals(0, rt_40546_count);
5213 
5214         t.getSelectionModel().clearSelection();
5215         assertEquals(0, rt_40546_count);
5216 
5217         p.clear();
5218         assertEquals(0, rt_40546_count);
5219 
5220         p.add(new Person("FirstName1", "LastName1", ""));
5221         assertEquals(0, rt_40546_count);
5222         p.add(new Person("FirstName2", "LastName2", ""));
5223         assertEquals(0, rt_40546_count);
5224         p.add(new Person("FirstName3", "LastName3", ""));
5225         assertEquals(0, rt_40546_count);
5226     }
5227 }