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