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