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