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