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