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