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